fixtury 0.2.1 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +7 -5
- data/lib/fixtury.rb +3 -0
- data/lib/fixtury/definition.rb +18 -45
- data/lib/fixtury/definition_executor.rb +78 -0
- data/lib/fixtury/reference.rb +7 -2
- data/lib/fixtury/schema.rb +36 -11
- data/lib/fixtury/store.rb +1 -4
- data/lib/fixtury/test_hooks.rb +21 -14
- data/lib/fixtury/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5419cf56fd821e06b8b0fc7d55f564d87e85aaab5e7e21933c5ef923f3f32294
|
4
|
+
data.tar.gz: e53da80eda66ee2abd07baec7ccd847ba18a5e8631b01014f7e62a43616ac509
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a69c33f32f35346abae60a3f3c27f232dc813fbe551f99055d7e2fcc8eacd5c6873fdf5a925be4d3ef3635c7558577918018dc8c399674e26232fc0fd0a0d10e
|
7
|
+
data.tar.gz: 4b9e9f3f0c4e538248eda219cb57d6f618a870407ae8f9bfeabf17aab49850e8e253bfd77a7279a9b70b164b572fb41021211b22b46d6e656c7fc57e21af6959
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# Fixtury
|
2
2
|
|
3
|
-
|
3
|
+
Fixtury aims to provide an interface for creating, managing, and accessing test data in a simple and efficient way. It has no opinion on how you generate the data, it simply provides efficient ways to access it.
|
4
4
|
|
5
|
-
|
5
|
+
Often, fixture frameworks require you to either heavily maintain static fixtures or generate all your fixtures at runtime. Fixtury attempts to find a middle ground that enables a faster and more effecient development process.
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
For example, if a developer is running a test locally in their development environment there's no reason to build all fixtures for your suite of 30k tests. Instead, if we're able to track the fixture dependencies of the tests that are running we can build (and cache) the data relevant for the specific tests that are run.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class MyTest < ::ActiveSupport::TestCase
|
11
|
+
include ::Fixtury::TestHooks
|
9
12
|
|
10
13
|
fixtury "users.fresh"
|
11
14
|
let(:user) { fixtury("users.fresh") }
|
@@ -15,7 +18,6 @@ class MyTest < ::Test
|
|
15
18
|
end
|
16
19
|
|
17
20
|
end
|
18
|
-
|
19
21
|
```
|
20
22
|
|
21
23
|
Loading this file would ensure `users.fresh` is loaded into the fixture set before the suite is run. In the context of ActiveSupport::TestCase, the Fixtury::Hooks file will ensure the database records are present prior to your suite running. Setting `use_transactional_fixtures` ensures all records are rolled back prior to running another test.
|
data/lib/fixtury.rb
CHANGED
@@ -10,11 +10,14 @@ require "fixtury/store"
|
|
10
10
|
|
11
11
|
module Fixtury
|
12
12
|
|
13
|
+
# Shortcut for opening the top level schema.
|
13
14
|
def self.define(&block)
|
14
15
|
schema.define(&block)
|
15
16
|
schema
|
16
17
|
end
|
17
18
|
|
19
|
+
# The default top level schema. Fixtury::Schema instances can be completely self-contained but most
|
20
|
+
# usage would be through this shared definition.
|
18
21
|
def self.schema
|
19
22
|
@top_level_schema ||= ::Fixtury::Schema.new(parent: nil, name: "")
|
20
23
|
end
|
data/lib/fixtury/definition.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "fixtury/definition_executor"
|
4
|
+
|
3
5
|
module Fixtury
|
4
6
|
class Definition
|
5
7
|
|
6
8
|
attr_reader :name
|
7
9
|
attr_reader :schema
|
10
|
+
alias parent schema
|
11
|
+
attr_reader :options
|
8
12
|
|
9
13
|
attr_reader :callable
|
10
14
|
attr_reader :enhancements
|
11
15
|
|
12
|
-
def initialize(schema: nil, name:, &block)
|
16
|
+
def initialize(schema: nil, name:, options: {}, &block)
|
13
17
|
@name = name
|
14
18
|
@schema = schema
|
15
19
|
@callable = block
|
20
|
+
@options = options
|
16
21
|
@enhancements = []
|
17
22
|
end
|
18
23
|
|
@@ -24,55 +29,23 @@ module Fixtury
|
|
24
29
|
@enhancements.any?
|
25
30
|
end
|
26
31
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
value
|
34
|
-
end
|
32
|
+
def info
|
33
|
+
{
|
34
|
+
name: name,
|
35
|
+
loc: location_from_callable(callable),
|
36
|
+
enhancements: enhancements.map { |e| location_from_callable(e) },
|
37
|
+
}
|
35
38
|
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
return yield unless store
|
41
|
-
|
42
|
-
store.with_relative_schema(schema) do
|
43
|
-
yield
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def run_callable(store:, callable:, execution_context: nil, value:)
|
48
|
-
args = []
|
49
|
-
args << value unless value.nil?
|
50
|
-
if callable.arity > args.length
|
51
|
-
raise ArgumentError, "A store store must be provided if the definition expects it." unless store
|
52
|
-
|
53
|
-
args << store
|
54
|
-
end
|
55
|
-
|
56
|
-
provide_execution_context_hooks(execution_context) do |ctxt|
|
57
|
-
if args.length.positive?
|
58
|
-
ctxt.instance_exec(*args, &callable)
|
59
|
-
else
|
60
|
-
ctxt.instance_eval(&callable)
|
61
|
-
end
|
62
|
-
end
|
40
|
+
def call(store: nil, execution_context: nil)
|
41
|
+
executor = ::Fixtury::DefinitionExecutor.new(store: store, definition: self, execution_context: execution_context)
|
42
|
+
executor.__call
|
63
43
|
end
|
64
44
|
|
65
|
-
def
|
66
|
-
return
|
45
|
+
def location_from_callable(callable)
|
46
|
+
return nil unless callable.respond_to?(:source_location)
|
67
47
|
|
68
|
-
|
69
|
-
value = if execution_context.respond_to?(:around_fixture)
|
70
|
-
execution_context.around_fixture(self) { yield execution_context }
|
71
|
-
else
|
72
|
-
yield execution_context
|
73
|
-
end
|
74
|
-
execution_context.after_fixture(self, value) if execution_context.respond_to?(:after_fixture)
|
75
|
-
value
|
48
|
+
callable.source_location.join(":")
|
76
49
|
end
|
77
50
|
|
78
51
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fixtury
|
4
|
+
class DefinitionExecutor
|
5
|
+
|
6
|
+
attr_reader :value, :execution_type, :definition, :store, :execution_context
|
7
|
+
|
8
|
+
def initialize(store: nil, execution_context: nil, definition:)
|
9
|
+
@store = store
|
10
|
+
@definition = definition
|
11
|
+
@execution_context = execution_context
|
12
|
+
@execution_type = nil
|
13
|
+
@value = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def __call
|
17
|
+
maybe_set_store_context do
|
18
|
+
provide_schema_hooks do
|
19
|
+
run_callable(callable: definition.callable, type: :definition)
|
20
|
+
definition.enhancements.each do |e|
|
21
|
+
run_callable(callable: e, type: :enhancement)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(name)
|
30
|
+
raise ArgumentError, "A store is required for #{definition.name}" unless store
|
31
|
+
|
32
|
+
store.get(name, execution_context: execution_context)
|
33
|
+
end
|
34
|
+
alias [] get
|
35
|
+
|
36
|
+
def method_missing(method_name, *args, &block)
|
37
|
+
return super unless execution_context
|
38
|
+
|
39
|
+
execution_context.send(method_name, *args, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to_missing?(method_name)
|
43
|
+
return super unless execution_context
|
44
|
+
|
45
|
+
execution_context.respond_to?(method_name, true)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def run_callable(callable:, type:)
|
51
|
+
@execution_type = type
|
52
|
+
|
53
|
+
@value = if callable.arity.positive?
|
54
|
+
instance_exec(self, &callable)
|
55
|
+
else
|
56
|
+
instance_eval(&callable)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def maybe_set_store_context
|
61
|
+
return yield unless store
|
62
|
+
|
63
|
+
store.with_relative_schema(definition.schema) do
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def provide_schema_hooks
|
69
|
+
return yield unless definition.schema
|
70
|
+
|
71
|
+
@value = definition.schema.around_fixture_hook(self) do
|
72
|
+
yield
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/fixtury/reference.rb
CHANGED
@@ -9,12 +9,17 @@ module Fixtury
|
|
9
9
|
new(name, HOLDER_VALUE)
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
def self.create(name, value)
|
13
|
+
new(name, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :name, :value, :created_at, :options
|
13
17
|
|
14
|
-
def initialize(name, value)
|
18
|
+
def initialize(name, value, options = {})
|
15
19
|
@name = name
|
16
20
|
@value = value
|
17
21
|
@created_at = Time.now.to_i
|
22
|
+
@options = options
|
18
23
|
end
|
19
24
|
|
20
25
|
def holder?
|
data/lib/fixtury/schema.rb
CHANGED
@@ -9,16 +9,38 @@ require "fixtury/errors/schema_frozen_error"
|
|
9
9
|
module Fixtury
|
10
10
|
class Schema
|
11
11
|
|
12
|
-
attr_reader :definitions, :children, :name, :parent, :relative_name
|
12
|
+
attr_reader :definitions, :children, :name, :parent, :relative_name, :around_fixture_definition, :options
|
13
13
|
|
14
|
-
def initialize(parent:, name:)
|
14
|
+
def initialize(parent:, name:, options: {})
|
15
15
|
@name = name
|
16
16
|
@parent = parent
|
17
17
|
@relative_name = @name.split("/").last
|
18
|
+
@around_fixture_definition = nil
|
19
|
+
@options = options
|
18
20
|
@frozen = false
|
19
21
|
reset!
|
20
22
|
end
|
21
23
|
|
24
|
+
def around_fixture(&block)
|
25
|
+
@around_fixture_definition = block
|
26
|
+
end
|
27
|
+
|
28
|
+
def around_fixture_hook(executor, &definition)
|
29
|
+
maybe_invoke_parent_around_fixture_hook(executor) do
|
30
|
+
if around_fixture_definition.nil?
|
31
|
+
yield
|
32
|
+
else
|
33
|
+
around_fixture_definition.call(executor, definition)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def maybe_invoke_parent_around_fixture_hook(executor, &block)
|
39
|
+
return yield unless parent
|
40
|
+
|
41
|
+
parent.around_fixture_hook(executor, &block)
|
42
|
+
end
|
43
|
+
|
22
44
|
def reset!
|
23
45
|
@children = {}
|
24
46
|
@definitions = {}
|
@@ -62,19 +84,19 @@ module Fixtury
|
|
62
84
|
out.join("\n")
|
63
85
|
end
|
64
86
|
|
65
|
-
def namespace(name, &block)
|
87
|
+
def namespace(name, options = {}, &block)
|
66
88
|
ensure_not_frozen!
|
67
89
|
ensure_no_conflict!(name: name, definitions: true, namespaces: false)
|
68
90
|
|
69
|
-
child = find_or_create_child_schema(name: name)
|
91
|
+
child = find_or_create_child_schema(name: name, options: options)
|
70
92
|
child.instance_eval(&block) if block_given?
|
71
93
|
child
|
72
94
|
end
|
73
95
|
|
74
|
-
def fixture(name, &block)
|
96
|
+
def fixture(name, options = {}, &block)
|
75
97
|
ensure_not_frozen!
|
76
98
|
ensure_no_conflict!(name: name, definitions: true, namespaces: true)
|
77
|
-
create_child_definition(name: name, &block)
|
99
|
+
create_child_definition(name: name, options: options, &block)
|
78
100
|
end
|
79
101
|
|
80
102
|
def enhance(name, &block)
|
@@ -99,6 +121,8 @@ module Fixtury
|
|
99
121
|
end
|
100
122
|
end
|
101
123
|
|
124
|
+
around_fixture(&other_ns.around_fixture_definition) if other_ns.around_fixture_definition
|
125
|
+
|
102
126
|
self
|
103
127
|
end
|
104
128
|
|
@@ -163,31 +187,32 @@ module Fixtury
|
|
163
187
|
children[name.to_s]
|
164
188
|
end
|
165
189
|
|
166
|
-
def find_or_create_child_schema(name:)
|
190
|
+
def find_or_create_child_schema(name:, options:)
|
167
191
|
name = name.to_s
|
168
192
|
child = find_child_schema(name: name)
|
169
193
|
child ||= begin
|
170
194
|
children[name] = begin
|
171
195
|
child_name = build_child_name(name: name)
|
172
|
-
self.class.new(name: child_name, parent: self)
|
196
|
+
self.class.new(name: child_name, parent: self, options: options)
|
173
197
|
end
|
174
198
|
end
|
199
|
+
child
|
175
200
|
end
|
176
201
|
|
177
202
|
def find_child_definition(name:)
|
178
203
|
definitions[name.to_s]
|
179
204
|
end
|
180
205
|
|
181
|
-
def create_child_definition(name:, &block)
|
206
|
+
def create_child_definition(name:, options:, &block)
|
182
207
|
child_name = build_child_name(name: name)
|
183
|
-
definition = ::Fixtury::Definition.new(name: child_name, schema: self, &block)
|
208
|
+
definition = ::Fixtury::Definition.new(name: child_name, schema: self, options: options, &block)
|
184
209
|
definitions[name.to_s] = definition
|
185
210
|
end
|
186
211
|
|
187
212
|
def build_child_name(name:)
|
188
213
|
name = name&.to_s
|
189
214
|
raise ArgumentError, "`name` must be provided" if name.nil?
|
190
|
-
raise ArgumentError, "#{name} is invalid. `name` must contain only a-z, A-Z, 0-9, and _." unless
|
215
|
+
raise ArgumentError, "#{name} is invalid. `name` must contain only a-z, A-Z, 0-9, and _." unless /^[a-zA-Z_0-9]+$/.match?(name)
|
191
216
|
|
192
217
|
arr = ["", self.name, name]
|
193
218
|
arr.join("/").gsub(%r{/{2,}}, "/")
|
data/lib/fixtury/store.rb
CHANGED
@@ -21,7 +21,6 @@ module Fixtury
|
|
21
21
|
attr_reader :filepath, :references, :ttl, :auto_refresh_expired
|
22
22
|
attr_reader :schema, :locator
|
23
23
|
attr_reader :log_level
|
24
|
-
attr_reader :execution_context
|
25
24
|
|
26
25
|
def initialize(
|
27
26
|
filepath: nil,
|
@@ -29,7 +28,6 @@ module Fixtury
|
|
29
28
|
log_level: nil,
|
30
29
|
ttl: nil,
|
31
30
|
schema: nil,
|
32
|
-
execution_context: nil,
|
33
31
|
auto_refresh_expired: false
|
34
32
|
)
|
35
33
|
@schema = schema || ::Fixtury.schema
|
@@ -39,7 +37,6 @@ module Fixtury
|
|
39
37
|
@locator = locator
|
40
38
|
@filepath = filepath
|
41
39
|
@references = @filepath && ::File.file?(@filepath) ? ::YAML.load_file(@filepath) : {}
|
42
|
-
@execution_context = execution_context
|
43
40
|
@ttl = ttl ? ttl.to_i : ttl
|
44
41
|
@auto_refresh_expired = !!auto_refresh_expired
|
45
42
|
self.class.instance ||= self
|
@@ -107,7 +104,7 @@ module Fixtury
|
|
107
104
|
result
|
108
105
|
end
|
109
106
|
|
110
|
-
def get(name)
|
107
|
+
def get(name, execution_context: nil)
|
111
108
|
dfn = schema.get_definition!(name)
|
112
109
|
full_name = dfn.name
|
113
110
|
ref = references[full_name]
|
data/lib/fixtury/test_hooks.rb
CHANGED
@@ -11,6 +11,9 @@ module Fixtury
|
|
11
11
|
included do
|
12
12
|
class_attribute :fixtury_dependencies
|
13
13
|
self.fixtury_dependencies = Set.new
|
14
|
+
|
15
|
+
class_attribute :local_fixtury_dependencies
|
16
|
+
self.local_fixtury_dependencies = Set.new
|
14
17
|
end
|
15
18
|
|
16
19
|
module ClassMethods
|
@@ -22,7 +25,7 @@ module Fixtury
|
|
22
25
|
if block_given?
|
23
26
|
raise ArgumentError, "A fixture cannot be defined in an anonymous class" if name.nil?
|
24
27
|
|
25
|
-
namespace =
|
28
|
+
namespace = fixtury_namespace
|
26
29
|
|
27
30
|
ns = ::Fixtury.schema
|
28
31
|
|
@@ -32,7 +35,7 @@ module Fixtury
|
|
32
35
|
|
33
36
|
names.each do |fixture_name|
|
34
37
|
ns.fixture(fixture_name, &definition)
|
35
|
-
self.
|
38
|
+
self.local_fixtury_dependencies += ["/#{namespace}/#{fixture_name}"]
|
36
39
|
end
|
37
40
|
|
38
41
|
# otherwise, just record the dependency
|
@@ -40,14 +43,16 @@ module Fixtury
|
|
40
43
|
self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
|
41
44
|
end
|
42
45
|
|
43
|
-
|
46
|
+
accessor_option = opts.key?(:accessor) ? opts[:accessor] : true
|
47
|
+
|
48
|
+
if accessor_option
|
44
49
|
|
45
|
-
if
|
50
|
+
if accessor_option != true && names.length > 1
|
46
51
|
raise ArgumentError, "A named :accessor option is only available when providing one fixture"
|
47
52
|
end
|
48
53
|
|
49
54
|
names.each do |fixture_name|
|
50
|
-
method_name =
|
55
|
+
method_name = accessor_option == true ? fixture_name.split("/").last : accessor_option
|
51
56
|
ivar = :"@#{method_name}"
|
52
57
|
|
53
58
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
@@ -62,6 +67,10 @@ module Fixtury
|
|
62
67
|
end
|
63
68
|
end
|
64
69
|
|
70
|
+
def fixtury_namespace
|
71
|
+
name.underscore
|
72
|
+
end
|
73
|
+
|
65
74
|
end
|
66
75
|
|
67
76
|
def fixtury(name)
|
@@ -69,18 +78,16 @@ module Fixtury
|
|
69
78
|
|
70
79
|
name = name.to_s
|
71
80
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
return fixtury_store.get(local_name)
|
76
|
-
end
|
81
|
+
local_alias = "/#{self.class.fixtury_namespace}/#{name}"
|
82
|
+
if self.local_fixtury_dependencies.include?(local_alias)
|
83
|
+
return fixtury_store.get(local_alias, execution_context: self)
|
77
84
|
end
|
78
85
|
|
79
86
|
unless self.fixtury_dependencies.include?(name)
|
80
87
|
raise ArgumentError, "Unrecognized fixtury dependency `#{name}` for #{self.class}"
|
81
88
|
end
|
82
89
|
|
83
|
-
fixtury_store.get(name)
|
90
|
+
fixtury_store.get(name, execution_context: self)
|
84
91
|
end
|
85
92
|
|
86
93
|
def fixtury_store
|
@@ -99,7 +106,7 @@ module Fixtury
|
|
99
106
|
|
100
107
|
# piggybacking activerecord fixture setup for now.
|
101
108
|
def setup_fixtures(*args)
|
102
|
-
if fixtury_dependencies.any?
|
109
|
+
if fixtury_dependencies.any? || local_fixtury_dependencies.any?
|
103
110
|
setup_fixtury_fixtures
|
104
111
|
else
|
105
112
|
super
|
@@ -108,7 +115,7 @@ module Fixtury
|
|
108
115
|
|
109
116
|
# piggybacking activerecord fixture setup for now.
|
110
117
|
def teardown_fixtures(*args)
|
111
|
-
if fixtury_dependencies.any?
|
118
|
+
if fixtury_dependencies.any? || local_fixtury_dependencies.any?
|
112
119
|
teardown_fixtury_fixtures
|
113
120
|
else
|
114
121
|
super
|
@@ -139,7 +146,7 @@ module Fixtury
|
|
139
146
|
end
|
140
147
|
|
141
148
|
def load_all_fixtury_fixtures!
|
142
|
-
fixtury_dependencies.each do |name|
|
149
|
+
(fixtury_dependencies | local_fixtury_dependencies).each do |name|
|
143
150
|
fixtury(name) unless fixtury_loaded?(name)
|
144
151
|
end
|
145
152
|
end
|
data/lib/fixtury/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fixtury
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: autotest
|
@@ -155,6 +155,7 @@ files:
|
|
155
155
|
- fixtury.gemspec
|
156
156
|
- lib/fixtury.rb
|
157
157
|
- lib/fixtury/definition.rb
|
158
|
+
- lib/fixtury/definition_executor.rb
|
158
159
|
- lib/fixtury/errors/already_defined_error.rb
|
159
160
|
- lib/fixtury/errors/circular_dependency_error.rb
|
160
161
|
- lib/fixtury/errors/fixture_not_defined_error.rb
|