fixtury 0.4.1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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:)
|