fixtury 0.4.1 → 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +48 -44
- data/README.md +4 -4
- data/fixtury.gemspec +6 -6
- data/lib/fixtury/definition.rb +39 -36
- data/lib/fixtury/definition_executor.rb +23 -52
- data/lib/fixtury/dependency.rb +52 -0
- data/lib/fixtury/dependency_store.rb +45 -0
- data/lib/fixtury/errors.rb +81 -0
- data/lib/fixtury/hooks.rb +90 -0
- data/lib/fixtury/locator.rb +38 -27
- data/lib/fixtury/locator_backend/common.rb +17 -19
- data/lib/fixtury/locator_backend/globalid.rb +8 -8
- data/lib/fixtury/locator_backend/memory.rb +7 -7
- data/lib/fixtury/mutation_observer.rb +140 -0
- data/lib/fixtury/path_resolver.rb +45 -0
- data/lib/fixtury/railtie.rb +4 -0
- data/lib/fixtury/reference.rb +12 -10
- data/lib/fixtury/schema.rb +21 -225
- data/lib/fixtury/schema_node.rb +113 -0
- data/lib/fixtury/store.rb +113 -68
- data/lib/fixtury/test_hooks.rb +60 -89
- data/lib/fixtury/version.rb +1 -1
- data/lib/fixtury.rb +44 -5
- metadata +33 -33
- data/lib/fixtury/errors/already_defined_error.rb +0 -13
- data/lib/fixtury/errors/circular_dependency_error.rb +0 -13
- data/lib/fixtury/errors/fixture_not_defined_error.rb +0 -13
- data/lib/fixtury/errors/option_collision_error.rb +0 -13
- data/lib/fixtury/errors/schema_frozen_error.rb +0 -13
- data/lib/fixtury/errors/unrecognizable_locator_error.rb +0 -11
- data/lib/fixtury/path.rb +0 -36
data/lib/fixtury/schema.rb
CHANGED
@@ -1,251 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "fixtury/definition"
|
4
|
-
require "fixtury/path"
|
5
|
-
require "fixtury/errors/already_defined_error"
|
6
|
-
require "fixtury/errors/fixture_not_defined_error"
|
7
|
-
require "fixtury/errors/schema_frozen_error"
|
8
|
-
require "fixtury/errors/option_collision_error"
|
9
|
-
|
10
3
|
module Fixtury
|
11
4
|
class Schema
|
12
5
|
|
13
|
-
|
14
|
-
|
15
|
-
def initialize(parent:, name:)
|
16
|
-
@name = name
|
17
|
-
@parent = parent
|
18
|
-
@relative_name = @name.split("/").last
|
19
|
-
@around_fixture_definition = nil
|
20
|
-
@options = {}
|
21
|
-
@frozen = false
|
22
|
-
reset!
|
23
|
-
end
|
24
|
-
|
25
|
-
def merge_options(opts = {})
|
26
|
-
opts.each_pair do |k, v|
|
27
|
-
if options.key?(k) && options[k] != v
|
28
|
-
raise ::Fixtury::Errors::OptionCollisionError.new(name, k, options[k], v)
|
29
|
-
end
|
30
|
-
|
31
|
-
options[k] = v
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def around_fixture(&block)
|
36
|
-
@around_fixture_definition = block
|
37
|
-
end
|
38
|
-
|
39
|
-
def around_fixture_hook(executor, &definition)
|
40
|
-
maybe_invoke_parent_around_fixture_hook(executor) do
|
41
|
-
if around_fixture_definition.nil?
|
42
|
-
yield
|
43
|
-
else
|
44
|
-
around_fixture_definition.call(executor, definition)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def maybe_invoke_parent_around_fixture_hook(executor, &block)
|
50
|
-
return yield unless parent
|
51
|
-
|
52
|
-
parent.around_fixture_hook(executor, &block)
|
53
|
-
end
|
6
|
+
include ::Fixtury::SchemaNode
|
54
7
|
|
55
|
-
def
|
56
|
-
|
57
|
-
@definitions = {}
|
8
|
+
def initialize(name: "", **options)
|
9
|
+
super(name: name, **options)
|
58
10
|
end
|
59
11
|
|
60
|
-
def
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
def frozen?
|
65
|
-
!!@frozen
|
66
|
-
end
|
67
|
-
|
68
|
-
def top_level_schema
|
69
|
-
top_level_schema? ? self : parent.top_level_schema
|
70
|
-
end
|
71
|
-
|
72
|
-
def top_level_schema?
|
73
|
-
parent.nil?
|
12
|
+
def acts_like_fixtury_schema?
|
13
|
+
true
|
74
14
|
end
|
75
15
|
|
76
16
|
def define(&block)
|
77
|
-
ensure_not_frozen!
|
78
17
|
instance_eval(&block)
|
79
18
|
self
|
80
19
|
end
|
81
20
|
|
82
|
-
|
83
|
-
|
84
|
-
out = []
|
85
|
-
out << "#{indent}ns:#{relative_name}"
|
86
|
-
definitions.keys.sort.each do |key|
|
87
|
-
out << "#{indent} defn:#{key}"
|
88
|
-
end
|
89
|
-
|
90
|
-
children.keys.sort.each do |key|
|
91
|
-
child = children[key]
|
92
|
-
out << child.structure("#{indent} ")
|
93
|
-
end
|
94
|
-
|
95
|
-
out.join("\n")
|
96
|
-
end
|
97
|
-
|
98
|
-
def namespace(name, options = {}, &block)
|
99
|
-
ensure_not_frozen!
|
100
|
-
ensure_no_conflict!(name: name, definitions: true, namespaces: false)
|
101
|
-
|
102
|
-
child = find_or_create_child_schema(name: name, options: options)
|
103
|
-
child.instance_eval(&block) if block_given?
|
104
|
-
child
|
105
|
-
end
|
106
|
-
|
107
|
-
def fixture(name, options = {}, &block)
|
108
|
-
ensure_not_frozen!
|
109
|
-
ensure_no_conflict!(name: name, definitions: true, namespaces: true)
|
110
|
-
create_child_definition(name: name, options: options, &block)
|
111
|
-
end
|
112
|
-
|
113
|
-
def enhance(name, &block)
|
114
|
-
ensure_not_frozen!
|
115
|
-
definition = get_definition!(name)
|
116
|
-
definition.enhance(&block)
|
117
|
-
definition
|
118
|
-
end
|
119
|
-
|
120
|
-
def merge(other_ns)
|
121
|
-
ensure_not_frozen!
|
122
|
-
other_ns.definitions.each_pair do |name, dfn|
|
123
|
-
fixture(name, dfn.options, &dfn.callable)
|
124
|
-
dfn.enhancements.each do |e|
|
125
|
-
enhance(name, &e)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
other_ns.children.each_pair do |name, other_ns_child|
|
130
|
-
namespace(name, other_ns_child.options) do
|
131
|
-
merge(other_ns_child)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
around_fixture(&other_ns.around_fixture_definition) if other_ns.around_fixture_definition
|
136
|
-
|
137
|
-
self
|
138
|
-
end
|
139
|
-
|
140
|
-
def get_definition!(name)
|
141
|
-
dfn = get_definition(name)
|
142
|
-
raise ::Fixtury::Errors::FixtureNotDefinedError, name unless dfn
|
143
|
-
|
144
|
-
dfn
|
145
|
-
end
|
146
|
-
|
147
|
-
def get_definition(name)
|
148
|
-
path = ::Fixtury::Path.new(namespace: self.name, path: name)
|
149
|
-
top_level = top_level_schema
|
150
|
-
|
151
|
-
dfn = nil
|
152
|
-
path.possible_absolute_paths.each do |abs_path|
|
153
|
-
*namespaces, definition_name = abs_path.split("/")
|
154
|
-
|
155
|
-
namespaces.shift if namespaces.first == top_level.name
|
156
|
-
target = top_level
|
157
|
-
|
158
|
-
namespaces.each do |ns|
|
159
|
-
next if ns.empty?
|
160
|
-
|
161
|
-
target = target.children[ns]
|
162
|
-
break unless target
|
163
|
-
end
|
164
|
-
|
165
|
-
dfn = target.definitions[definition_name] if target
|
166
|
-
return dfn if dfn
|
167
|
-
end
|
168
|
-
|
169
|
-
nil
|
21
|
+
def schema_node_type
|
22
|
+
first_ancestor? ? "schema" : "ns"
|
170
23
|
end
|
171
24
|
|
172
|
-
def
|
173
|
-
|
174
|
-
top_level = top_level_schema
|
25
|
+
def namespace(name, **options, &block)
|
26
|
+
child = get("./#{name}")
|
175
27
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
namespaces.shift if namespaces.first == top_level.name
|
180
|
-
target = top_level
|
181
|
-
|
182
|
-
namespaces.each do |ns|
|
183
|
-
next if ns.empty?
|
184
|
-
|
185
|
-
target = target.children[ns]
|
186
|
-
break unless target
|
187
|
-
end
|
188
|
-
|
189
|
-
return target if target
|
28
|
+
if child && !child.acts_like?(:fixtury_schema)
|
29
|
+
raise Errors::AlreadyDefinedError, child.pathname
|
190
30
|
end
|
191
31
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
protected
|
196
|
-
|
197
|
-
def find_child_schema(name:)
|
198
|
-
children[name.to_s]
|
199
|
-
end
|
200
|
-
|
201
|
-
def find_or_create_child_schema(name:, options:)
|
202
|
-
name = name.to_s
|
203
|
-
child = find_child_schema(name: name)
|
204
|
-
child ||= begin
|
205
|
-
children[name] = begin
|
206
|
-
child_name = build_child_name(name: name)
|
207
|
-
self.class.new(name: child_name, parent: self)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
child.merge_options(options)
|
32
|
+
child ||= self.class.new(name: name, parent: self)
|
33
|
+
child.apply_options!(options)
|
34
|
+
child.instance_eval(&block) if block_given?
|
211
35
|
child
|
212
36
|
end
|
213
37
|
|
214
|
-
def
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
definitions[name.to_s] = definition
|
222
|
-
end
|
223
|
-
|
224
|
-
def build_child_name(name:)
|
225
|
-
name = name&.to_s
|
226
|
-
raise ArgumentError, "`name` must be provided" if name.nil?
|
227
|
-
raise ArgumentError, "#{name} is invalid. `name` must contain only a-z, A-Z, 0-9, and _." unless /^[a-zA-Z_0-9]+$/.match?(name)
|
228
|
-
|
229
|
-
arr = ["", self.name, name]
|
230
|
-
arr.join("/").gsub(%r{/{2,}}, "/")
|
231
|
-
end
|
232
|
-
|
233
|
-
def ensure_no_conflict!(name:, namespaces:, definitions:)
|
234
|
-
if definitions
|
235
|
-
definition = find_child_definition(name: name)
|
236
|
-
raise ::Fixtury::Errors::AlreadyDefinedError, definition.name if definition
|
237
|
-
end
|
238
|
-
|
239
|
-
if namespaces
|
240
|
-
ns = find_child_schema(name: name)
|
241
|
-
raise ::Fixtury::Errors::AlreadyDefinedError, ns.name if ns
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
def ensure_not_frozen!
|
246
|
-
return unless frozen?
|
247
|
-
|
248
|
-
raise ::Fixtury::Errors::SchemaFrozenError
|
38
|
+
def fixture(name, **options, &block)
|
39
|
+
::Fixtury::Definition.new(
|
40
|
+
name: name,
|
41
|
+
parent: self,
|
42
|
+
**options,
|
43
|
+
&block
|
44
|
+
)
|
249
45
|
end
|
250
46
|
|
251
47
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Fixtury
|
2
|
+
module SchemaNode
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
VALID_NODE_NAME = /^[a-zA-Z0-9_]*$/
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_reader :name, :pathname, :parent, :children, :options
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name:, parent: nil, **options)
|
13
|
+
name = name.to_s
|
14
|
+
raise ArgumentError, "#{name.inspect} is an invalid node name" unless name.match?(VALID_NODE_NAME)
|
15
|
+
|
16
|
+
@name = name
|
17
|
+
@parent = parent
|
18
|
+
@pathname = File.join(*[parent&.pathname, "/", @name].compact).to_s
|
19
|
+
@children = {}
|
20
|
+
@options = {}
|
21
|
+
apply_options!(options)
|
22
|
+
@parent.add_child(self) if @parent
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#{self.class}(pathname: #{pathname.inspect}, children: #{children.size})"
|
27
|
+
end
|
28
|
+
|
29
|
+
def schema_node_type
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def acts_like_fixtury_schema_node?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def first_ancestor
|
38
|
+
first_ancestor? ? self : parent.first_ancestor
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_child(child)
|
42
|
+
if children.key?(child.name) && children[child.name] != child
|
43
|
+
raise Errors::AlreadyDefinedError, child.pathname
|
44
|
+
end
|
45
|
+
|
46
|
+
children[child.name] = child
|
47
|
+
end
|
48
|
+
|
49
|
+
def first_ancestor?
|
50
|
+
parent.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def isolation_key(default: true)
|
54
|
+
from_parent = parent&.isolation_key(default: nil)
|
55
|
+
return from_parent if from_parent
|
56
|
+
|
57
|
+
value = options[:isolate] || default
|
58
|
+
value = (value == true ? pathname : value&.to_s).presence
|
59
|
+
value == "/" ? nil : value # special case to accommodate root nodes
|
60
|
+
end
|
61
|
+
|
62
|
+
def get!(search)
|
63
|
+
thing = get(search)
|
64
|
+
raise Errors::SchemaNodeNotDefinedError.new(pathname, search) unless thing
|
65
|
+
|
66
|
+
thing
|
67
|
+
end
|
68
|
+
|
69
|
+
def get(search)
|
70
|
+
raise ArgumentError, "`search` must be provided" if search.blank?
|
71
|
+
|
72
|
+
resolver = Fixtury::PathResolver.new(namespace: self.pathname, search: search)
|
73
|
+
resolver.possible_absolute_paths.each do |path|
|
74
|
+
target = first_ancestor
|
75
|
+
segments = path.split("/")
|
76
|
+
segments.reject!(&:blank?)
|
77
|
+
segments.shift if segments.first == target.name
|
78
|
+
segments.each do |segment|
|
79
|
+
target = target.children[segment]
|
80
|
+
break unless target
|
81
|
+
end
|
82
|
+
|
83
|
+
return target if target
|
84
|
+
end
|
85
|
+
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
alias [] get
|
89
|
+
|
90
|
+
# helpful for inspection
|
91
|
+
def structure(prefix = "")
|
92
|
+
out = []
|
93
|
+
my_structure = +"#{prefix}#{schema_node_type}:#{name}"
|
94
|
+
my_structure << "(#{options.inspect})" if options.present?
|
95
|
+
out << my_structure
|
96
|
+
children.each_value do |child|
|
97
|
+
out << child.structure("#{prefix} ")
|
98
|
+
end
|
99
|
+
out.join("\n")
|
100
|
+
end
|
101
|
+
|
102
|
+
def apply_options!(opts = {})
|
103
|
+
opts.each do |key, value|
|
104
|
+
if options.key?(key) && options[key] != value
|
105
|
+
raise Errors::OptionCollisionError.new(name, key, options[key], value)
|
106
|
+
end
|
107
|
+
|
108
|
+
options[key] = value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/fixtury/store.rb
CHANGED
@@ -3,33 +3,36 @@
|
|
3
3
|
require "fileutils"
|
4
4
|
require "singleton"
|
5
5
|
require "yaml"
|
6
|
-
require "fixtury/locator"
|
7
|
-
require "fixtury/errors/circular_dependency_error"
|
8
|
-
require "fixtury/reference"
|
9
6
|
|
10
7
|
module Fixtury
|
11
8
|
class Store
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :
|
16
|
-
attr_reader :schema, :locator
|
10
|
+
attr_reader :filepath
|
11
|
+
attr_reader :loaded_isolation_keys
|
12
|
+
attr_reader :locator
|
17
13
|
attr_reader :log_level
|
14
|
+
attr_reader :references
|
15
|
+
attr_reader :schema
|
16
|
+
attr_reader :ttl
|
18
17
|
|
19
|
-
def initialize(
|
20
|
-
filepath: nil,
|
21
|
-
locator: ::Fixtury::Locator.instance,
|
22
|
-
ttl: nil,
|
23
|
-
schema: nil,
|
24
|
-
auto_refresh_expired: false
|
25
|
-
)
|
18
|
+
def initialize(filepath: nil, locator: nil, ttl: nil, schema: nil)
|
26
19
|
@schema = schema || ::Fixtury.schema
|
27
|
-
@locator = locator
|
20
|
+
@locator = locator || ::Fixtury::Locator.new
|
28
21
|
@filepath = filepath
|
29
|
-
@references =
|
30
|
-
@ttl = ttl
|
31
|
-
@
|
32
|
-
|
22
|
+
@references = load_reference_from_file || {}
|
23
|
+
@ttl = ttl&.to_i
|
24
|
+
@loaded_isolation_keys = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
parts = []
|
29
|
+
parts << "schema: #{schema.inspect}"
|
30
|
+
parts << "locator: #{locator.inspect}"
|
31
|
+
parts << "filepath: #{filepath.inspect}" if filepath
|
32
|
+
parts << "ttl: #{ttl.inspect}" if ttl
|
33
|
+
parts << "references: #{references.size}"
|
34
|
+
|
35
|
+
"#{self.class}(#{parts.join(", ")})"
|
33
36
|
end
|
34
37
|
|
35
38
|
def dump_to_file
|
@@ -37,36 +40,40 @@ module Fixtury
|
|
37
40
|
|
38
41
|
::FileUtils.mkdir_p(File.dirname(filepath))
|
39
42
|
|
40
|
-
writable = references.each_with_object({}) do |(
|
41
|
-
h[
|
43
|
+
writable = references.each_with_object({}) do |(pathname, ref), h|
|
44
|
+
h[pathname] = ref if ref.real?
|
42
45
|
end
|
43
46
|
|
44
|
-
::File.
|
47
|
+
::File.binwrite(filepath, writable.to_yaml)
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_reference_from_file
|
51
|
+
return unless filepath
|
52
|
+
return unless File.file?(filepath)
|
53
|
+
|
54
|
+
::YAML.unsafe_load_file(filepath)
|
45
55
|
end
|
46
56
|
|
47
|
-
def
|
57
|
+
def clear_stale_references!
|
48
58
|
return unless ttl
|
49
59
|
|
50
60
|
references.delete_if do |name, ref|
|
51
|
-
|
52
|
-
log("expiring #{name}", level: LOG_LEVEL_DEBUG) if
|
53
|
-
|
61
|
+
stale = reference_stale?(ref)
|
62
|
+
log("expiring #{name}", level: LOG_LEVEL_DEBUG) if stale
|
63
|
+
stale
|
54
64
|
end
|
55
65
|
end
|
56
66
|
|
57
67
|
def load_all(schema = self.schema)
|
58
|
-
schema.
|
59
|
-
get(
|
60
|
-
|
61
|
-
|
62
|
-
schema.children.each_pair do |_key, ns|
|
63
|
-
load_all(ns)
|
68
|
+
schema.children.each_value do |item|
|
69
|
+
get(item.name) if item.acts_like?(:fixtury_definition)
|
70
|
+
load_all(item) if item.acts_like?(:fixtury_schema)
|
64
71
|
end
|
65
72
|
end
|
66
73
|
|
67
74
|
def clear_cache!(pattern: nil)
|
68
75
|
pattern ||= "*"
|
69
|
-
pattern = "
|
76
|
+
pattern = "/#{pattern}" unless pattern.start_with?("/")
|
70
77
|
glob = pattern.end_with?("*")
|
71
78
|
pattern = pattern[0...-1] if glob
|
72
79
|
references.delete_if do |key, _value|
|
@@ -86,73 +93,111 @@ module Fixtury
|
|
86
93
|
end
|
87
94
|
|
88
95
|
def loaded?(name)
|
89
|
-
dfn = schema.
|
90
|
-
|
91
|
-
ref = references[full_name]
|
96
|
+
dfn = schema.get!(name)
|
97
|
+
ref = references[dfn.pathname]
|
92
98
|
result = ref&.real?
|
93
|
-
log(result ? "hit #{
|
99
|
+
log(result ? "hit #{dfn.pathname}" : "miss #{dfn.pathname}", level: LOG_LEVEL_ALL)
|
94
100
|
result
|
95
101
|
end
|
96
102
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
def loaded_or_loading?(pathname)
|
104
|
+
!!references[pathname]
|
105
|
+
end
|
106
|
+
|
107
|
+
def maybe_load_isolation_dependencies(definition)
|
108
|
+
isolation_key = definition.isolation_key
|
109
|
+
return if loaded_isolation_keys[isolation_key]
|
101
110
|
|
111
|
+
load_isolation_dependencies(isolation_key, schema.first_ancestor)
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_isolation_dependencies(isolation_key, target_schema)
|
115
|
+
loaded_isolation_keys[isolation_key] = true
|
116
|
+
target_schema.children.each_value do |child|
|
117
|
+
if child.acts_like?(:fixtury_definition)
|
118
|
+
next unless child.isolation_key == isolation_key
|
119
|
+
next if loaded_or_loading?(child.pathname)
|
120
|
+
get(child.pathname)
|
121
|
+
elsif child.acts_like?(:fixtury_schema)
|
122
|
+
load_isolation_dependencies(isolation_key, child)
|
123
|
+
else
|
124
|
+
raise NotImplementedError, "Unknown isolation loading behavior: #{child.class.name}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Fetch a fixture by name. This will load the fixture if it has not been loaded yet.
|
130
|
+
# If a definition contains an isolation key, all fixtures with the same isolation key will be loaded.
|
131
|
+
def get(name)
|
132
|
+
log("getting #{name}", level: LOG_LEVEL_DEBUG)
|
133
|
+
|
134
|
+
# Find the definition.
|
135
|
+
dfn = schema.get!(name)
|
136
|
+
raise ArgumentError, "#{name.inspect} must refer to a definition" unless dfn.acts_like?(:fixtury_definition)
|
137
|
+
|
138
|
+
pathname = dfn.pathname
|
139
|
+
|
140
|
+
# Ensure that if we're part of an isolation group, we load all the fixtures in that group.
|
141
|
+
maybe_load_isolation_dependencies(dfn)
|
142
|
+
|
143
|
+
# See if we already hold a reference to the fixture.
|
144
|
+
ref = references[pathname]
|
145
|
+
|
146
|
+
# If the reference is a placeholder, we have a circular dependency.
|
102
147
|
if ref&.holder?
|
103
|
-
raise
|
148
|
+
raise Errors::CircularDependencyError, pathname
|
104
149
|
end
|
105
150
|
|
106
|
-
|
107
|
-
|
108
|
-
|
151
|
+
# If the reference is stale, we should refresh it.
|
152
|
+
# We do so by clearing it from the store and setting the reference to nil.
|
153
|
+
if ref && reference_stale?(ref)
|
154
|
+
log("refreshing #{pathname}", level: LOG_LEVEL_DEBUG)
|
155
|
+
clear_reference(pathname)
|
109
156
|
ref = nil
|
110
157
|
end
|
111
158
|
|
112
159
|
value = nil
|
113
160
|
|
114
161
|
if ref
|
115
|
-
log("hit #{
|
116
|
-
value =
|
162
|
+
log("hit #{pathname}", level: LOG_LEVEL_ALL)
|
163
|
+
value = locator.load(ref.locator_key)
|
117
164
|
if value.nil?
|
118
|
-
|
119
|
-
|
165
|
+
clear_reference(pathname)
|
166
|
+
ref = nil
|
167
|
+
log("missing #{pathname}", level: LOG_LEVEL_ALL)
|
120
168
|
end
|
121
169
|
end
|
122
170
|
|
123
171
|
if value.nil?
|
124
172
|
# set the references to a holder value so any recursive behavior ends up hitting a circular dependency error if the same fixture load is attempted
|
125
|
-
references[
|
126
|
-
|
127
|
-
|
173
|
+
references[pathname] = ::Fixtury::Reference.holder(pathname)
|
174
|
+
|
175
|
+
begin
|
176
|
+
executor = ::Fixtury::DefinitionExecutor.new(store: self, definition: dfn)
|
177
|
+
value = executor.call
|
178
|
+
rescue StandardError
|
179
|
+
clear_reference(pathname)
|
180
|
+
raise
|
181
|
+
end
|
128
182
|
|
129
|
-
log("store #{
|
183
|
+
log("store #{pathname}", level: LOG_LEVEL_DEBUG)
|
130
184
|
|
131
|
-
|
132
|
-
|
133
|
-
references[full_name] = ref
|
185
|
+
locator_key = locator.dump(value, context: pathname)
|
186
|
+
references[pathname] = ::Fixtury::Reference.new(pathname, locator_key)
|
134
187
|
end
|
135
188
|
|
136
189
|
value
|
137
190
|
end
|
138
191
|
alias [] get
|
139
192
|
|
140
|
-
def
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
def dump_ref(_name, value)
|
145
|
-
locator.dump(value)
|
146
|
-
end
|
147
|
-
|
148
|
-
def clear_ref(name)
|
149
|
-
references.delete(name)
|
193
|
+
def clear_reference(pathname)
|
194
|
+
references.delete(pathname)
|
150
195
|
end
|
151
196
|
|
152
|
-
def
|
197
|
+
def reference_stale?(ref)
|
153
198
|
return true if ttl && ref.created_at < (Time.now.to_i - ttl)
|
154
199
|
|
155
|
-
!locator.
|
200
|
+
!locator.recognizable_key?(ref.locator_key)
|
156
201
|
end
|
157
202
|
|
158
203
|
def log(msg, level:)
|