fixtury 0.4.1 → 1.0.0.beta2
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/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)
|