fixtury 0.4.1 → 1.0.0.beta2
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/configuration.rb +117 -0
- data/lib/fixtury/definition.rb +34 -38
- 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 +52 -23
- data/lib/fixtury/locator_backend/common.rb +17 -19
- data/lib/fixtury/locator_backend/global_id.rb +32 -0
- data/lib/fixtury/locator_backend/memory.rb +9 -8
- 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 +47 -225
- data/lib/fixtury/schema_node.rb +175 -0
- data/lib/fixtury/store.rb +168 -84
- data/lib/fixtury/test_hooks.rb +125 -101
- data/lib/fixtury/version.rb +1 -1
- data/lib/fixtury.rb +84 -12
- metadata +35 -35
- 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/locator_backend/globalid.rb +0 -32
- data/lib/fixtury/path.rb +0 -36
- data/lib/fixtury/tasks.rake +0 -10
data/lib/fixtury/store.rb
CHANGED
@@ -1,82 +1,94 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent/atomic/thread_local_var"
|
3
4
|
require "fileutils"
|
4
5
|
require "singleton"
|
5
6
|
require "yaml"
|
6
|
-
require "fixtury/locator"
|
7
|
-
require "fixtury/errors/circular_dependency_error"
|
8
|
-
require "fixtury/reference"
|
9
7
|
|
10
8
|
module Fixtury
|
9
|
+
# A store is a container for built fixture references. It is responsible for loading and caching fixtures
|
10
|
+
# based on a schema and a locator.
|
11
11
|
class Store
|
12
12
|
|
13
|
-
|
13
|
+
attr_reader :locator
|
14
|
+
attr_reader :schema
|
15
|
+
attr_reader :ttl
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
locator: ::Fixtury::Locator.instance,
|
22
|
-
ttl: nil,
|
23
|
-
schema: nil,
|
24
|
-
auto_refresh_expired: false
|
25
|
-
)
|
17
|
+
# Create a new store.
|
18
|
+
# @param locator [Fixtury::Locator, Symbol, NilClass] (see Fixtury::Locator#from)
|
19
|
+
# @param ttl [Integer, NilClass] The time-to-live for references in seconds.
|
20
|
+
# @param schema [Fixtury::Schema, NilClass] The schema to use for fixture definitions, defaults to the global schema.
|
21
|
+
# @return [Fixtury::Store]
|
22
|
+
def initialize(locator: nil, ttl: nil, schema: nil)
|
26
23
|
@schema = schema || ::Fixtury.schema
|
27
|
-
@locator = locator
|
28
|
-
@
|
29
|
-
|
30
|
-
@ttl = ttl ? ttl.to_i : ttl
|
31
|
-
@auto_refresh_expired = !!auto_refresh_expired
|
32
|
-
self.class.instance ||= self
|
24
|
+
@locator = ::Fixtury::Locator.from(locator)
|
25
|
+
@ttl = ttl&.to_i
|
26
|
+
self.references = ::Fixtury.configuration.stored_references
|
33
27
|
end
|
34
28
|
|
35
|
-
def
|
36
|
-
|
29
|
+
def references
|
30
|
+
@references ||= ::Concurrent::ThreadLocalVar.new({})
|
31
|
+
@references.value
|
32
|
+
end
|
37
33
|
|
38
|
-
|
34
|
+
def references=(value)
|
35
|
+
references.clear
|
36
|
+
@references.value = value
|
37
|
+
end
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def loaded_isolation_keys
|
40
|
+
@loaded_isolation_keys ||= ::Concurrent::ThreadLocalVar.new({})
|
41
|
+
@loaded_isolation_keys.value
|
42
|
+
end
|
43
43
|
|
44
|
-
|
44
|
+
# Empty the store of any references and loaded isolation keys.
|
45
|
+
def reset
|
46
|
+
references.clear
|
47
|
+
loaded_isolation_keys.clear
|
45
48
|
end
|
46
49
|
|
47
|
-
|
48
|
-
|
50
|
+
# Summarize the current state of the store.
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
def inspect
|
54
|
+
parts = []
|
55
|
+
parts << "schema: #{schema.inspect}"
|
56
|
+
parts << "locator: #{locator.inspect}"
|
57
|
+
parts << "ttl: #{ttl.inspect}" if ttl
|
58
|
+
parts << "references: #{references.size}"
|
59
|
+
|
60
|
+
"#{self.class}(#{parts.join(", ")})"
|
61
|
+
end
|
49
62
|
|
63
|
+
# Clear any references that are beyond their ttl or are no longer recognizable by the locator.
|
64
|
+
#
|
65
|
+
# @return [void]
|
66
|
+
def clear_stale_references!
|
50
67
|
references.delete_if do |name, ref|
|
51
|
-
|
52
|
-
log("expiring #{name}", level: LOG_LEVEL_DEBUG) if
|
53
|
-
|
68
|
+
stale = reference_stale?(ref)
|
69
|
+
log("expiring #{name}", level: LOG_LEVEL_DEBUG) if stale
|
70
|
+
stale
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|
74
|
+
# Load all fixtures in the target schema, defaulting to the store's schema.
|
75
|
+
# This will load all fixtures in the schema and any child schemas.
|
76
|
+
#
|
77
|
+
# @param schema [Fixtury::Schema] The schema to load, defaults to the store's schema.
|
78
|
+
# @return [void]
|
57
79
|
def load_all(schema = self.schema)
|
58
|
-
schema.
|
59
|
-
get(
|
80
|
+
schema.children.each_value do |item|
|
81
|
+
get(item.name) if item.acts_like?(:fixtury_definition)
|
82
|
+
load_all(item) if item.acts_like?(:fixtury_schema)
|
60
83
|
end
|
61
|
-
|
62
|
-
schema.children.each_pair do |_key, ns|
|
63
|
-
load_all(ns)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def clear_cache!(pattern: nil)
|
68
|
-
pattern ||= "*"
|
69
|
-
pattern = "/" + pattern unless pattern.start_with?("/")
|
70
|
-
glob = pattern.end_with?("*")
|
71
|
-
pattern = pattern[0...-1] if glob
|
72
|
-
references.delete_if do |key, _value|
|
73
|
-
hit = glob ? key.start_with?(pattern) : key == pattern
|
74
|
-
log("clearing #{key}", level: LOG_LEVEL_DEBUG) if hit
|
75
|
-
hit
|
76
|
-
end
|
77
|
-
dump_to_file
|
78
84
|
end
|
79
85
|
|
86
|
+
# Temporarily set a contextual schema to use for loading fixtures. This is
|
87
|
+
# useful when evaluating dependencies of a definition while still storing the results.
|
88
|
+
#
|
89
|
+
# @param schema [Fixtury::Schema] The schema to use.
|
90
|
+
# @yield [void] The block to execute with the given schema.
|
91
|
+
# @return [Object] The result of the block
|
80
92
|
def with_relative_schema(schema)
|
81
93
|
prior = @schema
|
82
94
|
@schema = schema
|
@@ -85,76 +97,148 @@ module Fixtury
|
|
85
97
|
@schema = prior
|
86
98
|
end
|
87
99
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
100
|
+
# Is a fixture for the given search already loaded?
|
101
|
+
#
|
102
|
+
# @param search [String] The name of the fixture to search for.
|
103
|
+
# @return [TrueClass, FalseClass] `true` if the fixture is loaded, `false` otherwise.
|
104
|
+
def loaded?(search)
|
105
|
+
dfn = schema.get!(search)
|
106
|
+
ref = references[dfn.pathname]
|
92
107
|
result = ref&.real?
|
93
|
-
log(result ? "hit #{
|
108
|
+
log(result ? "hit #{dfn.pathname}" : "miss #{dfn.pathname}", level: LOG_LEVEL_ALL)
|
94
109
|
result
|
95
110
|
end
|
96
111
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
112
|
+
# Fetch a fixture by name. This will load the fixture if it has not been loaded yet.
|
113
|
+
# If a definition contains an isolation key, all fixtures with the same isolation key will be loaded.
|
114
|
+
#
|
115
|
+
# @param search [String] The name of the fixture to search for.
|
116
|
+
# @return [Object] The loaded fixture.
|
117
|
+
# @raise [Fixtury::Errors::CircularDependencyError] if a circular dependency is detected.
|
118
|
+
# @raise [Fixtury::Errors::SchemaNodeNotDefinedError] if the search does not return a node.
|
119
|
+
# @raise [Fixtury::Errors::UnknownDefinitionError] if the search does not return a definition.
|
120
|
+
# @raise [Fixtury::Errors::DefinitionExecutorError] if the definition executor fails.
|
121
|
+
def get(search)
|
122
|
+
log("getting #{search} relative to #{schema.pathname}", level: LOG_LEVEL_DEBUG)
|
123
|
+
|
124
|
+
# Find the definition.
|
125
|
+
dfn = schema.get!(search)
|
126
|
+
raise ArgumentError, "#{search.inspect} must refer to a definition" unless dfn.acts_like?(:fixtury_definition)
|
101
127
|
|
128
|
+
pathname = dfn.pathname
|
129
|
+
|
130
|
+
# Ensure that if we're part of an isolation group, we load all the fixtures in that group.
|
131
|
+
maybe_load_isolation_dependencies(dfn.isolation_key)
|
132
|
+
|
133
|
+
# See if we already hold a reference to the fixture.
|
134
|
+
ref = references[pathname]
|
135
|
+
|
136
|
+
# If the reference is a placeholder, we have a circular dependency.
|
102
137
|
if ref&.holder?
|
103
|
-
raise
|
138
|
+
raise Errors::CircularDependencyError, pathname
|
104
139
|
end
|
105
140
|
|
106
|
-
|
107
|
-
|
108
|
-
|
141
|
+
# If the reference is stale, we should refresh it.
|
142
|
+
# We do so by clearing it from the store and setting the reference to nil.
|
143
|
+
if ref && reference_stale?(ref)
|
144
|
+
log("refreshing #{pathname}", level: LOG_LEVEL_DEBUG)
|
145
|
+
clear_reference(pathname)
|
109
146
|
ref = nil
|
110
147
|
end
|
111
148
|
|
112
149
|
value = nil
|
113
150
|
|
114
151
|
if ref
|
115
|
-
log("hit #{
|
116
|
-
value =
|
152
|
+
log("hit #{pathname}", level: LOG_LEVEL_ALL)
|
153
|
+
value = locator.load(ref.locator_key)
|
117
154
|
if value.nil?
|
118
|
-
|
119
|
-
|
155
|
+
clear_reference(pathname)
|
156
|
+
ref = nil
|
157
|
+
log("missing #{pathname}", level: LOG_LEVEL_ALL)
|
120
158
|
end
|
121
159
|
end
|
122
160
|
|
123
161
|
if value.nil?
|
124
162
|
# 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
|
-
|
163
|
+
references[pathname] = ::Fixtury::Reference.holder(pathname)
|
164
|
+
|
165
|
+
begin
|
166
|
+
executor = ::Fixtury::DefinitionExecutor.new(store: self, definition: dfn)
|
167
|
+
value = executor.call
|
168
|
+
rescue StandardError
|
169
|
+
clear_reference(pathname)
|
170
|
+
raise
|
171
|
+
end
|
128
172
|
|
129
|
-
log("store #{
|
173
|
+
log("store #{pathname}", level: LOG_LEVEL_DEBUG)
|
130
174
|
|
131
|
-
|
132
|
-
|
133
|
-
references[full_name] = ref
|
175
|
+
locator_key = locator.dump(value, context: pathname)
|
176
|
+
references[pathname] = ::Fixtury::Reference.new(pathname, locator_key)
|
134
177
|
end
|
135
178
|
|
136
179
|
value
|
137
180
|
end
|
138
181
|
alias [] get
|
139
182
|
|
140
|
-
|
141
|
-
|
183
|
+
protected
|
184
|
+
|
185
|
+
# Determine if the given pathname is already loaded or is currently being loaded.
|
186
|
+
#
|
187
|
+
# @param pathname [String] The pathname to check.
|
188
|
+
# @return [TrueClass, FalseClass] `true` if the pathname is already loaded or is currently being loaded, `false` otherwise.
|
189
|
+
def loaded_or_loading?(pathname)
|
190
|
+
!!references[pathname]
|
142
191
|
end
|
143
192
|
|
144
|
-
|
145
|
-
|
193
|
+
# Load all fixtures with the given isolation key in the target schema
|
194
|
+
# if we're not already attempting to load them.
|
195
|
+
def maybe_load_isolation_dependencies(isolation_key)
|
196
|
+
return if loaded_isolation_keys[isolation_key]
|
197
|
+
loaded_isolation_keys[isolation_key] = true
|
198
|
+
|
199
|
+
load_isolation_dependencies(isolation_key, schema.first_ancestor)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Load all fixtures with the given isolation key in the target schema.
|
203
|
+
#
|
204
|
+
# @param isolation_key [String] The isolation key to load fixtures for.
|
205
|
+
# @param target_schema [Fixtury::Schema] The schema to search within.
|
206
|
+
# @return [void]
|
207
|
+
def load_isolation_dependencies(isolation_key, target_schema)
|
208
|
+
target_schema.children.each_value do |child|
|
209
|
+
if child.acts_like?(:fixtury_definition)
|
210
|
+
next unless child.isolation_key == isolation_key
|
211
|
+
next if loaded_or_loading?(child.pathname)
|
212
|
+
|
213
|
+
get(child.pathname)
|
214
|
+
elsif child.acts_like?(:fixtury_schema)
|
215
|
+
load_isolation_dependencies(isolation_key, child)
|
216
|
+
else
|
217
|
+
raise NotImplementedError, "Unknown isolation loading behavior: #{child.class.name}"
|
218
|
+
end
|
219
|
+
end
|
146
220
|
end
|
147
221
|
|
148
|
-
|
149
|
-
|
222
|
+
# Remove a reference at the given pathname from the stored references.
|
223
|
+
#
|
224
|
+
# @param pathname [String] The pathname to remove.
|
225
|
+
# @return [void]
|
226
|
+
def clear_reference(pathname)
|
227
|
+
references.delete(pathname)
|
150
228
|
end
|
151
229
|
|
152
|
-
|
230
|
+
# Determine if a reference is stale. A reference is stale if it is beyond its ttl or
|
231
|
+
# if it is no longer recognizable by the locator.
|
232
|
+
#
|
233
|
+
# @param ref [Fixtury::Reference] The reference to check.
|
234
|
+
# @return [TrueClass, FalseClass] `true` if the reference is stale, `false` otherwise.
|
235
|
+
def reference_stale?(ref)
|
153
236
|
return true if ttl && ref.created_at < (Time.now.to_i - ttl)
|
154
237
|
|
155
|
-
!locator.
|
238
|
+
!locator.recognizable_key?(ref.locator_key)
|
156
239
|
end
|
157
240
|
|
241
|
+
# Log a contextual message using Fixtury.log
|
158
242
|
def log(msg, level:)
|
159
243
|
::Fixtury.log(msg, level: level, name: "store")
|
160
244
|
end
|
data/lib/fixtury/test_hooks.rb
CHANGED
@@ -1,166 +1,190 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "fixtury
|
3
|
+
require "fixtury"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
5
|
|
6
6
|
module Fixtury
|
7
|
+
# TestHooks is a module designed to hook into a Minitest test case, and
|
8
|
+
# provide a way to load fixtures into the test case. It is designed to be
|
9
|
+
# prepended into the test case class, and will automatically load fixtures
|
10
|
+
# before the test case is setup, and rollback any changes after the test
|
11
|
+
# case is torn down.
|
12
|
+
#
|
13
|
+
# The module also provides a way to define fixture dependencies, and will
|
14
|
+
# automatically load those dependencies before the test case is setup.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class MyTest < Minitest::Test
|
18
|
+
# prepend Fixtury::TestHooks
|
19
|
+
#
|
20
|
+
# fixtury "user"
|
21
|
+
# fixtury "post"
|
22
|
+
#
|
23
|
+
# def test_something
|
24
|
+
# user # => returns the `users` fixture
|
25
|
+
# user.do_some_mutation
|
26
|
+
# assert_equal 1, user.mutations.count
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # In the above example, the `users` and `posts` fixtures will be loaded
|
31
|
+
# # before the test case is setup, and any changes will be rolled back
|
32
|
+
# # after the test case is torn down.
|
33
|
+
#
|
34
|
+
# # The `fixtury` method also accepts a `:as` option, which can be used to
|
35
|
+
# # define a named accessor method for a fixture. This is useful when
|
36
|
+
# # defining a single fixture, and you want to access it using a different
|
37
|
+
# # name. If no `:as` option is provided, the fixture will be accessed
|
38
|
+
# # using the last segment of the fixture's pathname.
|
39
|
+
#
|
40
|
+
# class MyTest < Minitest::Test
|
41
|
+
# prepend Fixtury::TestHooks
|
42
|
+
#
|
43
|
+
# fixtury "/my/user_record", as: :user
|
44
|
+
#
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# A Set object named fixtury_dependencies is made available on the test class.
|
48
|
+
# This allows you to load all Minitest runnables and analyze what fixtures are
|
49
|
+
# needed. This is very helpful in CI pipelines when you want to prepare all fixtures
|
50
|
+
# ahead of time to share between multiple processes.
|
51
|
+
#
|
52
|
+
# The setup and teardown attempt to manage a transaction for each registered database
|
53
|
+
# connection if ActiveRecord::Base is present. If use_transaction_tests or use_transactional_fixtures
|
54
|
+
# are present, those settings will be respected. If neither are present, a transaction will be used.
|
7
55
|
module TestHooks
|
8
56
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
57
|
+
def self.prepended(klass)
|
58
|
+
klass.class_attribute :fixtury_dependencies
|
59
|
+
klass.fixtury_dependencies = Set.new
|
60
|
+
klass.extend ClassMethods
|
61
|
+
end
|
14
62
|
|
15
|
-
|
16
|
-
|
63
|
+
def self.included(klass)
|
64
|
+
raise ArgumentError, "#{name} should be prepended, not included"
|
17
65
|
end
|
18
66
|
|
19
67
|
module ClassMethods
|
20
68
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
ns = ns.namespace(ns_name){}
|
34
|
-
end
|
35
|
-
|
36
|
-
names.map! do |fixture_name|
|
37
|
-
ns.fixture(fixture_name, &definition)
|
38
|
-
new_name = "/#{namespace}/#{fixture_name}"
|
39
|
-
self.local_fixtury_dependencies += [new_name]
|
40
|
-
new_name
|
41
|
-
end
|
42
|
-
|
43
|
-
# otherwise, just record the dependency
|
44
|
-
else
|
45
|
-
self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
|
69
|
+
# Declare fixtury dependencies for this test case. This will automatically
|
70
|
+
# load the fixtures before the test case is setup, and rollback any changes
|
71
|
+
# after the test case is torn down.
|
72
|
+
#
|
73
|
+
# @param searches [Array<String>] A list of fixture names to load. These should be resolvable paths relative to Fixtury.schema (root).
|
74
|
+
# @param opts [Hash] A list of options to customize the behavior of the fixtures.
|
75
|
+
# @option opts [Symbol, String, Boolean] :as (true) The name of the accessor method to define for the fixture. If true (default), the last segment will be used.
|
76
|
+
# @return [void]
|
77
|
+
def fixtury(*searches, **opts)
|
78
|
+
pathnames = searches.map do |search|
|
79
|
+
dfn = Fixtury.schema.get!(search)
|
80
|
+
dfn.pathname
|
46
81
|
end
|
47
82
|
|
83
|
+
self.fixtury_dependencies += pathnames
|
84
|
+
|
48
85
|
accessor_option = opts[:as]
|
49
86
|
accessor_option = opts[:accessor] if accessor_option.nil? # old version, backwards compatability
|
50
87
|
accessor_option = accessor_option.nil? ? true : accessor_option
|
51
88
|
|
52
89
|
if accessor_option
|
53
90
|
|
54
|
-
if accessor_option != true &&
|
91
|
+
if accessor_option != true && pathnames.length > 1
|
55
92
|
raise ArgumentError, "A named :as option is only available when providing one fixture"
|
56
93
|
end
|
57
94
|
|
58
|
-
|
59
|
-
method_name = accessor_option == true ?
|
60
|
-
|
95
|
+
pathnames.each do |pathname|
|
96
|
+
method_name = (accessor_option == true ? pathname.split("/").last : accessor_option).to_sym
|
97
|
+
|
98
|
+
if method_defined?(method_name)
|
99
|
+
raise ArgumentError, "A method by the name of #{method_name} already exists in #{self}"
|
100
|
+
end
|
101
|
+
|
102
|
+
ivar = :"@fixtury_#{method_name}"
|
61
103
|
|
62
104
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
63
105
|
def #{method_name}
|
64
106
|
return #{ivar} if defined?(#{ivar})
|
65
107
|
|
66
|
-
|
67
|
-
#{ivar} = value
|
108
|
+
#{ivar} = fixtury("#{pathname}")
|
68
109
|
end
|
69
110
|
EV
|
70
111
|
end
|
71
112
|
end
|
72
113
|
end
|
73
114
|
|
74
|
-
def fixtury_namespace
|
75
|
-
name.underscore
|
76
|
-
end
|
77
|
-
|
78
115
|
end
|
79
116
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# in the case that we're looking for a relative fixture, see if it's registered relative to the test's namespace.
|
86
|
-
unless name.include?("/")
|
87
|
-
local_name = "/#{self.class.fixtury_namespace}/#{name}"
|
88
|
-
if local_fixtury_dependencies.include?(local_name)
|
89
|
-
return fixtury_store.get(local_name, execution_context: self)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
unless fixtury_dependencies.include?(name) || local_fixtury_dependencies.include?(name)
|
94
|
-
raise ArgumentError, "Unrecognized fixtury dependency `#{name}` for #{self.class}"
|
95
|
-
end
|
96
|
-
|
97
|
-
fixtury_store.get(name, execution_context: self)
|
117
|
+
# Minitest before_setup hook. This will load the fixtures before the test.
|
118
|
+
def before_setup(...)
|
119
|
+
fixtury_setup if fixtury_dependencies.any?
|
120
|
+
super
|
98
121
|
end
|
99
122
|
|
100
|
-
|
101
|
-
|
123
|
+
# Minitest after_teardown hook. This will rollback any changes made to the fixtures after the test.
|
124
|
+
def after_teardown(...)
|
125
|
+
super
|
126
|
+
fixtury_teardown if fixtury_dependencies.any?
|
102
127
|
end
|
103
128
|
|
104
|
-
|
105
|
-
|
129
|
+
# Access a fixture via a search term. This will access the fixture from the Fixtury store.
|
130
|
+
# If the fixture was not declared as a dependency, an error will be raised.
|
131
|
+
#
|
132
|
+
# @param search [String] The search term to use to find the fixture.
|
133
|
+
# @return [Object] The fixture.
|
134
|
+
# @raise [Fixtury::Errors::UnknownTestDependencyError] if the search term does not result in a declared dependency.
|
135
|
+
# @raise [Fixtury::Errors::SchemaNodeNotDefinedError] if the search term does not result in a recognized fixture.
|
136
|
+
def fixtury(search)
|
137
|
+
dfn = Fixtury.schema.get!(search)
|
138
|
+
|
139
|
+
unless fixtury_dependencies.include?(dfn.pathname)
|
140
|
+
raise Errors::UnknownTestDependencyError, "Unrecognized fixtury dependency `#{dfn.pathname}` for #{self.class}"
|
141
|
+
end
|
106
142
|
|
107
|
-
|
143
|
+
Fixtury.store.get(dfn.pathname)
|
108
144
|
end
|
109
145
|
|
146
|
+
# Retrieve all database connections that are currently registered with a writing role.
|
147
|
+
#
|
148
|
+
# @return [Array<ActiveRecord::ConnectionAdapters::AbstractAdapter>] The list of database connections.
|
110
149
|
def fixtury_database_connections
|
111
|
-
ActiveRecord::Base
|
112
|
-
end
|
150
|
+
return [] unless defined?(ActiveRecord::Base)
|
113
151
|
|
114
|
-
|
115
|
-
def setup_fixtures(*args)
|
116
|
-
if fixtury_dependencies.any? || local_fixtury_dependencies.any?
|
117
|
-
setup_fixtury_fixtures
|
118
|
-
else
|
119
|
-
super
|
120
|
-
end
|
152
|
+
ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
|
121
153
|
end
|
122
154
|
|
123
|
-
#
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
else
|
128
|
-
super
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def setup_fixtury_fixtures
|
155
|
+
# Load all dependenct fixtures and begin a transaction for each database connection.
|
156
|
+
def fixtury_setup
|
157
|
+
Fixtury.store.clear_stale_references!
|
158
|
+
fixtury_load_all_fixtures!
|
133
159
|
return unless fixtury_use_transactions?
|
134
160
|
|
135
|
-
clear_expired_fixtury_fixtures!
|
136
|
-
load_all_fixtury_fixtures!
|
137
|
-
|
138
161
|
fixtury_database_connections.each do |conn|
|
139
162
|
conn.begin_transaction joinable: false
|
140
163
|
end
|
141
164
|
end
|
142
165
|
|
143
|
-
|
166
|
+
# Rollback any changes made to the fixtures
|
167
|
+
def fixtury_teardown
|
144
168
|
return unless fixtury_use_transactions?
|
145
169
|
|
146
|
-
fixtury_database_connections.each
|
170
|
+
fixtury_database_connections.each do |conn|
|
171
|
+
conn.rollback_transaction if conn.open_transactions.positive?
|
172
|
+
end
|
147
173
|
end
|
148
174
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
175
|
+
# Load all fixture dependencies that have not previously been loaded into the store.
|
176
|
+
#
|
177
|
+
# @return [void]
|
178
|
+
def fixtury_load_all_fixtures!
|
179
|
+
fixtury_dependencies.each do |name|
|
180
|
+
next if Fixtury.store.loaded?(name)
|
154
181
|
|
155
|
-
|
156
|
-
|
157
|
-
unless fixtury_loaded?(name)
|
158
|
-
::Fixtury.log("preloading #{name.inspect}", name: "test", level: ::Fixtury::LOG_LEVEL_INFO)
|
159
|
-
fixtury(name)
|
160
|
-
end
|
182
|
+
::Fixtury.log("preloading #{name.inspect}", name: "test", level: ::Fixtury::LOG_LEVEL_INFO)
|
183
|
+
fixtury(name)
|
161
184
|
end
|
162
185
|
end
|
163
186
|
|
187
|
+
# Adhere to common Rails test transaction settings.
|
164
188
|
def fixtury_use_transactions?
|
165
189
|
return use_transactional_tests if respond_to?(:use_transactional_tests)
|
166
190
|
return use_transactional_fixtures if respond_to?(:use_transactional_fixtures)
|