ephemeron 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 032a79004b77bffee915f105cd807beb01fcc60c52e829c478fcf615a8fa8184
4
+ data.tar.gz: 65168d9788376856e6745c083f2a0d467b18deace844719e59024e9027a47db4
5
+ SHA512:
6
+ metadata.gz: 88ddba6984c039180c74433d9dc9447eae247b040700f3de02c0338e6a9ffe95dc3472c7eb982b81a867044d1c214c15cafe0ede0bf9286308d15f59664e403b
7
+ data.tar.gz: 6488f41b9df17130dbedb9f1db5bfa72e1aeded337863e46fe03e898728ed6aa5dfac2666f8cdaaf7004bf0623900f1073195327e06653994d4b974a17f67f1a
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Zbigniew Humeniuk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Ephemeron
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'ephemeron'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install ephemeron
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Ephemeron'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ require 'bundler/gem_tasks'
20
+
21
+ require 'rake/testtask'
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << 'test'
25
+ t.pattern = 'test/**/*_test.rb'
26
+ t.verbose = false
27
+ end
28
+
29
+ task default: :test
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ephemeron/railtie'
4
+
5
+ require 'ephemeron/addons/controller_addons'
6
+ require 'ephemeron/addons/model_addons'
7
+
8
+ require 'ephemeron/writers/thread_store'
9
+ require 'ephemeron/writers/callbacks'
10
+ require 'ephemeron/writers/logger'
11
+ require 'ephemeron/writers/store'
12
+
13
+ require 'ephemeron/errors'
14
+ require 'ephemeron/logger/convert'
15
+ require 'ephemeron/logger/finders'
16
+ require 'ephemeron/settings'
17
+ require 'ephemeron/store/convert'
18
+ require 'ephemeron/store/update'
19
+ require 'ephemeron/validators/before_save'
20
+ require 'ephemeron/validators/store'
21
+ require 'ephemeron/validators/store_element'
22
+ require 'ephemeron/validators/store/allowed_but_unsaved'
23
+ require 'ephemeron/validators/store/unused'
24
+
25
+ module Ephemeron
26
+ module_function
27
+
28
+ def configure
29
+ Ephemeron::Settings::CONFIG.new.tap do |config|
30
+ yield config
31
+ Ephemeron::Settings.configure config
32
+ end
33
+ end
34
+
35
+ def state
36
+ Store.store
37
+ end
38
+
39
+ def logs
40
+ Logger.logs
41
+ end
42
+
43
+ def add(model, as: nil)
44
+ Store.add model, as: as
45
+ end
46
+
47
+ def proxy
48
+ Logger.log nil, :proxy_start
49
+ yield
50
+ Logger.log nil, :proxy_end
51
+ end
52
+
53
+ def get(key)
54
+ Store.get key
55
+ end
56
+
57
+ def used(model_s)
58
+ log_used = ->(model) { Logger.log model, :used }
59
+ if model_s.is_a? ActiveRecord::Base
60
+ log_used.call model_s
61
+ else
62
+ model_s.each(&log_used)
63
+ end
64
+ model_s
65
+ end
66
+
67
+ def allow_save!(model)
68
+ model.tap { |m| Logger.log m, :save_allowed }
69
+ end
70
+
71
+ def skip_save!(model)
72
+ Logger.log model, :skip_save
73
+ end
74
+
75
+ def after_save!(&block)
76
+ Callbacks.add block
77
+ end
78
+
79
+ def finish
80
+ if Store.aliases[nil] != :abort
81
+ Store.validate
82
+ Store.persist
83
+ Callbacks.call
84
+ end
85
+ reset
86
+ end
87
+
88
+ def stop
89
+ Store.aliases[nil] = :abort
90
+ end
91
+
92
+ def reset
93
+ Store.reset
94
+ Logger.reset
95
+ Callbacks.reset
96
+ true
97
+ end
98
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module ControllerAddons
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_action -> { Ephemeron.finish }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module ModelAddons
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_find lambda { |model|
9
+ Ephemeron.add model
10
+ }
11
+
12
+ after_validation lambda { |model|
13
+ Logger.log model, :used, soft: true
14
+ }
15
+
16
+ before_save lambda { |model|
17
+ Validators::BeforeSave.call model
18
+ }
19
+
20
+ after_create lambda { |model|
21
+ Store::Update.call model
22
+ }
23
+
24
+ after_save lambda { |model|
25
+ Logger.log model, :saved
26
+ }
27
+
28
+ after_destroy lambda { |model|
29
+ Ephemeron::Store.destroy model
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class LoggerError < ArgumentError
5
+ end
6
+
7
+ class StoreError < StandardError
8
+ end
9
+
10
+ class PersistenceError < StandardError
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Logger
5
+ module Convert
6
+ module_function
7
+
8
+ def call(model, event)
9
+ "#{Store::Convert.call(model) if model} #{event}".strip
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Logger
5
+ module Finders
6
+ module_function
7
+
8
+ def find_all_events_for(model, *events)
9
+ Logger.logs
10
+ .filter { |l| events.any? { |e| l == Logger::Convert.call(model, e) } }
11
+ end
12
+
13
+ def find_first_allowed_but_unsaved
14
+ ary = Logger.logs
15
+ .reverse
16
+ .map { |line| line.split ' ' }
17
+ .find { |arr| %w[save_allowed saved].include?(arr[1]) }
18
+ return nil if ary.nil? || ary[1] == 'saved'
19
+
20
+ Store.store[ary[0]]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Railtie < ::Rails::Railtie
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Settings
5
+ CONFIG = Struct.new :skip_models
6
+
7
+ class << self
8
+ attr_writer :skipped_models
9
+
10
+ def skipped_models
11
+ @skipped_models || []
12
+ end
13
+
14
+ def configure(config)
15
+ Ephemeron::Settings.skipped_models = config.skip_models
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Store
5
+ class Convert
6
+ class << self
7
+ def call(model_s, as_new_record: false)
8
+ return class_name_and_id(model_s, as_new_record) if model_s.is_a?(ActiveRecord::Base)
9
+
10
+ model_s.map { |model| class_name_and_id(model) }.join ', '
11
+ end
12
+
13
+ private
14
+
15
+ def class_name_and_id(model, as_new_record = false)
16
+ new_record = as_new_record || model.new_record?
17
+ name = model.class.name.downcase
18
+ id = new_record ? "new_record##{model.object_id}" : model.id
19
+ "#{name}##{id}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Store
5
+ module Update
6
+ module_function
7
+
8
+ def call(model)
9
+ return unless Store.has? model, as_new_record: true
10
+
11
+ Store.add model
12
+ Store.store.delete Convert.call(model, as_new_record: true)
13
+
14
+ Store.aliases.find_all { |_, v| v == Convert.call(model, as_new_record: true) }
15
+ .to_h.keys
16
+ .each { |key| Store.aliases[key] = Convert.call(model) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module Validators
5
+ module BeforeSave
6
+ module_function
7
+
8
+ def call(model)
9
+ return unless Ephemeron::Store.has? model
10
+
11
+ last_event = Ephemeron::Logger::Finders
12
+ .find_all_events_for(model, :save_allowed, :saved)
13
+ .last
14
+ return if last_event == Logger::Convert.call(model, :save_allowed)
15
+
16
+ msg = "prior persistence for #{Ephemeron::Store::Convert.call(model)} is prohibited"
17
+ raise(PersistenceError, msg)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module Validators
5
+ module Store
6
+ module_function
7
+
8
+ def call
9
+ AllowedButUnsaved.call
10
+ Unused.call
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module Validators
5
+ module Store
6
+ module AllowedButUnsaved
7
+ module_function
8
+
9
+ def call
10
+ model = Ephemeron::Logger::Finders.find_first_allowed_but_unsaved
11
+ return if model.nil?
12
+
13
+ raise(
14
+ PersistenceError,
15
+ "save allowed for #{Ephemeron::Store::Convert.call(model)} but never saved"
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module Validators
5
+ module Store
6
+ module Unused
7
+ module_function
8
+
9
+ def call
10
+ models = Ephemeron::Store
11
+ .store
12
+ .filter { |_, model| !model.changed? && !Logger.has?(model, :used) }
13
+ .values
14
+ return if models.empty?
15
+
16
+ model_ids = Ephemeron::Store::Convert.call(models)
17
+ raise(
18
+ StoreError,
19
+ "#{model_ids} were added to store but neither fetched nor changed"
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ module Validators
5
+ module StoreElement
6
+ module_function
7
+
8
+ def call(model)
9
+ return false if Logger.logs.last == Logger::Convert.call(nil, :proxy_start)
10
+ return false unless model.is_a?(ActiveRecord::Base)
11
+ return false if Settings.skipped_models.include? model.class.name
12
+
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Callbacks < ThreadStore
5
+ attribute :ts_after_save_callbacks
6
+
7
+ def after_save_callbacks
8
+ self.ts_after_save_callbacks ||= []
9
+ end
10
+
11
+ def add(callback)
12
+ after_save_callbacks << callback
13
+ end
14
+
15
+ def call
16
+ after_save_callbacks.each(&:call)
17
+ end
18
+
19
+ def reset
20
+ self.ts_after_save_callbacks = []
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Logger < ThreadStore
5
+ attribute :ts_logs
6
+
7
+ PROXY_EVENTS = %i[proxy_start proxy_end].freeze
8
+ VALID_EVENTS = %i[saved save_allowed skip_save used] + PROXY_EVENTS
9
+
10
+ def logs
11
+ self.ts_logs ||= []
12
+ end
13
+
14
+ def has?(model, event)
15
+ validate model, event
16
+ logs.include? Convert.call(model, event)
17
+ end
18
+
19
+ def log(model, event, soft: false)
20
+ validate model, event
21
+ logs << Convert.call(model, event)
22
+ rescue LoggerError, StoreError => e
23
+ raise e unless soft
24
+ end
25
+
26
+ def reset
27
+ self.ts_logs = []
28
+ end
29
+
30
+ private
31
+
32
+ def validate(model, event)
33
+ raise(LoggerError, "event (#{event}) is invalid") if VALID_EVENTS.exclude?(event)
34
+ return true if validate_proxy_events model, event
35
+
36
+ raise(StoreError, "can't #{event_name_for_validate event} for nil") if model.nil?
37
+ return true if Store.has?(model)
38
+ return true if event == :saved
39
+
40
+ raise StoreError, "can't #{event_name_for_validate event} " \
41
+ "if #{Store::Convert.call model} is not in store"
42
+ end
43
+
44
+ def validate_proxy_events(model, event)
45
+ return false if PROXY_EVENTS.exclude?(event)
46
+ return true if model.nil?
47
+
48
+ raise LoggerError, 'Proxy events can be logged for nil only'
49
+ end
50
+
51
+ def event_name_for_validate(event)
52
+ if event == :used
53
+ 'mark as used'
54
+ else
55
+ 'log'
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class Store < ThreadStore
5
+ attribute :ts_store, :ts_aliases
6
+
7
+ def store
8
+ self.ts_store ||= {}
9
+ end
10
+
11
+ def aliases
12
+ self.ts_aliases ||= {}
13
+ end
14
+
15
+ def has?(model, as_new_record: false)
16
+ !store[Convert.call(model, as_new_record: as_new_record)].nil?
17
+ end
18
+
19
+ def add(model, as: nil)
20
+ return unless Validators::StoreElement.call(model)
21
+
22
+ key = Convert.call model
23
+ in_store = !store[key].nil?
24
+ store[key] = model unless in_store
25
+ if !as.nil?
26
+ aliases[as] = key
27
+ elsif in_store
28
+ raise StoreError, "#{key} is already in the store"
29
+ end
30
+ model
31
+ end
32
+
33
+ def get(key)
34
+ store[aliases[key]].tap do |model|
35
+ Logger.log(model, :used) if model
36
+ end
37
+ end
38
+
39
+ def validate
40
+ Validators::Store.call
41
+ end
42
+
43
+ def persist
44
+ changed_models = store.filter { |_, v| v.changed? }.values
45
+ .filter { |model| !Logger.has?(model, :skip_save) }
46
+ ActiveRecord::Base.transaction do
47
+ changed_models.each do |model|
48
+ Ephemeron.allow_save! model
49
+ model.save!
50
+ end
51
+ end
52
+ end
53
+
54
+ def destroy(model)
55
+ key = Convert.call model
56
+ return if store[key].nil?
57
+
58
+ store.delete key
59
+ end
60
+
61
+ def reset
62
+ self.ts_store = {}
63
+ self.ts_aliases = {}
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ephemeron
4
+ class ThreadStore < ActiveSupport::CurrentAttributes
5
+ def reset
6
+ raise 'not implemented!'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :ephemeron do
4
+ # # Task goes here
5
+ # end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ephemeron
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zbigniew Humeniuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: jb
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.7.0
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.7.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-mocks
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.9'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.9'
61
+ - !ruby/object:Gem::Dependency
62
+ name: sqlite3
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ description: |
76
+ Ephemeron improves the performance of your app.
77
+ It takes on itself the persistence of the ActiveRecord objects.
78
+ It protects you from saving the same object many times.
79
+ It checks whether a fetched from a database object was used.
80
+ It allows you to eliminate the controller's before_actions that are unnecessarily called.
81
+ Ephemeron works in the context of the thread and does the bulk of its job (i.a. persistence) at the end of the thread's lifecycle.
82
+ Although, you can trigger the finalization at any given moment.
83
+ You don't have to make a distinction in the code for the part that is responsible for the domain logic and the other responsible for the application layers.
84
+ email:
85
+ - hello@artofcode.co
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - lib/ephemeron.rb
94
+ - lib/ephemeron/addons/controller_addons.rb
95
+ - lib/ephemeron/addons/model_addons.rb
96
+ - lib/ephemeron/errors.rb
97
+ - lib/ephemeron/logger/convert.rb
98
+ - lib/ephemeron/logger/finders.rb
99
+ - lib/ephemeron/railtie.rb
100
+ - lib/ephemeron/settings.rb
101
+ - lib/ephemeron/store/convert.rb
102
+ - lib/ephemeron/store/update.rb
103
+ - lib/ephemeron/validators/before_save.rb
104
+ - lib/ephemeron/validators/store.rb
105
+ - lib/ephemeron/validators/store/allowed_but_unsaved.rb
106
+ - lib/ephemeron/validators/store/unused.rb
107
+ - lib/ephemeron/validators/store_element.rb
108
+ - lib/ephemeron/version.rb
109
+ - lib/ephemeron/writers/callbacks.rb
110
+ - lib/ephemeron/writers/logger.rb
111
+ - lib/ephemeron/writers/store.rb
112
+ - lib/ephemeron/writers/thread_store.rb
113
+ - lib/tasks/ephemeron_tasks.rake
114
+ homepage: https://artofcode.co
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubygems_version: 3.1.2
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: The organic DDD
137
+ test_files: []