fixture_kit 0.2.0 → 0.4.0
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/README.md +54 -34
- data/lib/fixture_kit/cache.rb +50 -105
- data/lib/fixture_kit/configuration.rb +6 -20
- data/lib/fixture_kit/configuration_helper.rb +11 -0
- data/lib/fixture_kit/{definition_context.rb → definition.rb} +8 -9
- data/lib/fixture_kit/fixture.rb +29 -7
- data/lib/fixture_kit/{generator.rb → isolator.rb} +2 -2
- data/lib/fixture_kit/isolators/minitest_isolator.rb +32 -0
- data/lib/fixture_kit/isolators/rspec_isolator.rb +33 -0
- data/lib/fixture_kit/minitest.rb +43 -0
- data/lib/fixture_kit/registry.rb +21 -44
- data/lib/fixture_kit/rspec.rb +10 -32
- data/lib/fixture_kit/runner.rb +28 -71
- data/lib/fixture_kit/singleton.rb +5 -18
- data/lib/fixture_kit/sql_subscriber.rb +26 -0
- data/lib/fixture_kit/version.rb +1 -1
- data/lib/fixture_kit.rb +15 -14
- metadata +9 -9
- data/lib/fixture_kit/rspec/declaration.rb +0 -17
- data/lib/fixture_kit/rspec/generator.rb +0 -45
- data/lib/fixture_kit/sql_capture.rb +0 -37
- data/lib/fixture_kit/test_case/generator.rb +0 -37
- data/lib/fixture_kit/test_case.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9c12d67129c3c71b587913a62cea49c41f55a7ab9c0f4308961906517f7b6f7
|
|
4
|
+
data.tar.gz: 92e5dec6ccdb3084d5b699fd1f462fb145667dd46caeb7f65aba8d782244bdbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75484ce7d299f78263f0c268a39eacb4c33896075b90138ed6741e253b95d312144ced9481ae2e7e6328a4b8817bf8e566c7d56df86975e236b2f2a248643376
|
|
7
|
+
data.tar.gz: c7644edbec83c88df2d5cc24a206ccabd4d3f1077a199fd2317c81e7e128801a58ed86738b229ed679b6ca3c1b979fe0f2b5adbf2334ed4ab16dbd2fa04b3ccf
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Test data setup is slow. Every `Model.create!` or `FactoryBot.create` hits the d
|
|
|
8
8
|
|
|
9
9
|
## The Solution
|
|
10
10
|
|
|
11
|
-
FixtureKit caches database records as raw SQL INSERT statements.
|
|
11
|
+
FixtureKit caches database records as raw SQL INSERT statements. It executes your fixture definition once, captures the resulting database state, and generates optimized batch INSERT statements. Fixture loads then replay these statements directly: no ORM overhead, no callbacks, just fast SQL.
|
|
12
12
|
|
|
13
13
|
Combined with RSpec's transactional fixtures, each test runs in a transaction that rolls back—so cached data can be reused across tests without cleanup.
|
|
14
14
|
|
|
@@ -26,7 +26,7 @@ end
|
|
|
26
26
|
|
|
27
27
|
### 1. Define a Fixture
|
|
28
28
|
|
|
29
|
-
Create fixture files in `spec/fixture_kit
|
|
29
|
+
Create fixture files in `spec/fixture_kit/` (or `test/fixture_kit/` for test-unit/minitest-style setups). Use whatever method you prefer to create records.
|
|
30
30
|
|
|
31
31
|
**Using ActiveRecord directly:**
|
|
32
32
|
|
|
@@ -117,60 +117,80 @@ RSpec.configure do |config|
|
|
|
117
117
|
end
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
When you call `fixture "name"` in an example group, FixtureKit registers that fixture with its runner.
|
|
121
|
+
|
|
122
|
+
### 4. Configure Minitest
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# test/test_helper.rb
|
|
126
|
+
require "fixture_kit/minitest"
|
|
127
|
+
|
|
128
|
+
class ActiveSupport::TestCase
|
|
129
|
+
self.use_transactional_tests = true
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When you call `fixture "name"` in a test class, FixtureKit registers that fixture with its runner and mounts it during test setup.
|
|
134
|
+
|
|
120
135
|
## Configuration
|
|
121
136
|
|
|
122
137
|
```ruby
|
|
123
138
|
# spec/support/fixture_kit.rb
|
|
124
139
|
FixtureKit.configure do |config|
|
|
125
|
-
# Where fixture definitions live (default:
|
|
140
|
+
# Where fixture definitions live (default: fixture_kit).
|
|
141
|
+
# Framework entrypoints set a framework-specific default:
|
|
142
|
+
# - fixture_kit/rspec -> spec/fixture_kit
|
|
143
|
+
# - fixture_kit/minitest -> test/fixture_kit
|
|
126
144
|
config.fixture_path = Rails.root.join("spec/fixture_kit").to_s
|
|
127
145
|
|
|
128
146
|
# Where cache files are stored (default: tmp/cache/fixture_kit)
|
|
129
147
|
config.cache_path = Rails.root.join("tmp/cache/fixture_kit").to_s
|
|
130
148
|
|
|
131
|
-
#
|
|
132
|
-
config.
|
|
149
|
+
# Wrapper used to isolate generation work (default: FixtureKit::MinitestIsolator)
|
|
150
|
+
# config.isolator = FixtureKit::MinitestIsolator
|
|
151
|
+
# config.isolator = FixtureKit::RSpecIsolator
|
|
133
152
|
|
|
134
|
-
# Optional
|
|
135
|
-
#
|
|
136
|
-
#
|
|
153
|
+
# Optional callback, called right before a fixture cache is generated.
|
|
154
|
+
# Called on first generation and forced regeneration.
|
|
155
|
+
# Receives the fixture name as a String.
|
|
156
|
+
# config.on_cache = ->(fixture_name) { puts "cached #{fixture_name}" }
|
|
137
157
|
end
|
|
138
158
|
```
|
|
139
159
|
|
|
140
|
-
Custom
|
|
141
|
-
`#run` receives the
|
|
160
|
+
Custom isolators should subclass `FixtureKit::Isolator` and implement `#run`.
|
|
161
|
+
`#run` receives the generation block and should execute it in whatever lifecycle you need.
|
|
162
|
+
|
|
163
|
+
By default, FixtureKit uses `FixtureKit::MinitestIsolator`, which runs generation inside an internal `ActiveSupport::TestCase` and removes that harness case from minitest runnables.
|
|
164
|
+
|
|
165
|
+
When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpecIsolator`. It runs generation inside an internal RSpec example, and uses a null reporter so harness runs do not count toward suite example totals.
|
|
142
166
|
|
|
143
|
-
|
|
167
|
+
## Lifecycle
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
Fixture generation is managed by `FixtureKit::Runner`.
|
|
146
170
|
|
|
147
|
-
|
|
171
|
+
1. Calling `fixture "name"` registers the fixture with the runner.
|
|
172
|
+
2. Runner `start`:
|
|
173
|
+
- clears `cache_path` (unless preserve-cache is enabled),
|
|
174
|
+
- generates caches for all already-registered fixtures.
|
|
175
|
+
3. If new tests are loaded after start (for example, queue-mode CI runners), newly registered fixtures are cached immediately.
|
|
176
|
+
4. At test runtime, `fixture` mounts from cache and returns a `Repository`.
|
|
148
177
|
|
|
149
|
-
|
|
178
|
+
When runner start happens:
|
|
150
179
|
|
|
151
|
-
|
|
180
|
+
- `fixture_kit/rspec`: in `before(:suite)`.
|
|
181
|
+
- `fixture_kit/minitest`: lazily during test setup for the first test class that declares `fixture`.
|
|
152
182
|
|
|
153
183
|
### Preserving Cache Locally
|
|
154
184
|
|
|
155
|
-
If you want to skip cache clearing
|
|
185
|
+
If you want to skip cache clearing when the runner starts (e.g., to reuse caches across test runs during local development), set the `FIXTURE_KIT_PRESERVE_CACHE` environment variable:
|
|
156
186
|
|
|
157
187
|
```bash
|
|
158
188
|
FIXTURE_KIT_PRESERVE_CACHE=1 bundle exec rspec
|
|
159
189
|
```
|
|
160
190
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
### CI Setup
|
|
164
|
-
|
|
165
|
-
For CI, set `autogenerate` to `false`. FixtureKit will automatically generate any missing caches at suite start:
|
|
191
|
+
Truthy values are case-insensitive: `1`, `true`, `yes`.
|
|
166
192
|
|
|
167
|
-
|
|
168
|
-
FixtureKit.configure do |config|
|
|
169
|
-
config.autogenerate = !ENV["CI"]
|
|
170
|
-
end
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
This means CI "just works" - no need to pre-generate caches or commit them to the repository. The first test run will generate all caches, and subsequent runs (if caches are preserved between builds) will reuse them.
|
|
193
|
+
This is useful when you're iterating on tests and your fixture definitions haven't changed.
|
|
174
194
|
|
|
175
195
|
## Nested Fixtures
|
|
176
196
|
|
|
@@ -189,13 +209,13 @@ fixture "teams/sales"
|
|
|
189
209
|
|
|
190
210
|
## How It Works
|
|
191
211
|
|
|
192
|
-
1. **
|
|
212
|
+
1. **Cache generation**: FixtureKit executes your definition block inside the configured isolator, subscribes to `sql.active_record` notifications to track created/updated/deleted models, queries those model tables, and caches INSERT statements for current table contents.
|
|
193
213
|
|
|
194
|
-
2. **
|
|
214
|
+
2. **Mounting**: FixtureKit loads the cached JSON file, clears each tracked table, and executes the raw SQL INSERT statements directly. No ORM instantiation, no callbacks.
|
|
195
215
|
|
|
196
|
-
3. **
|
|
216
|
+
3. **Repository build**: FixtureKit resolves exposed records by model + id and returns a `Repository` for method-based access.
|
|
197
217
|
|
|
198
|
-
4. **Transaction isolation**:
|
|
218
|
+
4. **Transaction isolation**: Use framework transactions (`use_transactional_fixtures` in RSpec, `use_transactional_tests` in Minitest) so test writes roll back and cached data can be reused safely between tests.
|
|
199
219
|
|
|
200
220
|
### Cache Format
|
|
201
221
|
|
|
@@ -216,7 +236,7 @@ Caches are stored as JSON files in `tmp/cache/fixture_kit/`:
|
|
|
216
236
|
```
|
|
217
237
|
|
|
218
238
|
- **records**: Maps model names to their INSERT statements. Using model names (not table names) allows FixtureKit to use the correct database connection for multi-database setups.
|
|
219
|
-
- **exposed**: Maps fixture accessor names to their model class and ID for querying after cache replay
|
|
239
|
+
- **exposed**: Maps fixture accessor names to their model class and ID for querying after cache replay.
|
|
220
240
|
|
|
221
241
|
## Cache Management
|
|
222
242
|
|
|
@@ -225,7 +245,7 @@ Delete the cache directory to force regeneration:
|
|
|
225
245
|
rm -rf tmp/cache/fixture_kit
|
|
226
246
|
```
|
|
227
247
|
|
|
228
|
-
Caches are
|
|
248
|
+
Caches are cleared at runner start unless `FIXTURE_KIT_PRESERVE_CACHE` is truthy.
|
|
229
249
|
|
|
230
250
|
## Multi-Database Support
|
|
231
251
|
|
data/lib/fixture_kit/cache.rb
CHANGED
|
@@ -7,109 +7,61 @@ require "active_support/inflector"
|
|
|
7
7
|
|
|
8
8
|
module FixtureKit
|
|
9
9
|
class Cache
|
|
10
|
-
|
|
11
|
-
@memory_cache = {}
|
|
10
|
+
include ConfigurationHelper
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
attr_accessor :memory_cache
|
|
12
|
+
attr_reader :fixture
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
else
|
|
20
|
-
@memory_cache.clear
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Clear fixture cache (both memory and disk)
|
|
25
|
-
def clear(fixture_name = nil)
|
|
26
|
-
clear_memory_cache(fixture_name)
|
|
27
|
-
|
|
28
|
-
cache_path = FixtureKit.configuration.cache_path
|
|
29
|
-
if fixture_name
|
|
30
|
-
cache_file = File.join(cache_path, "#{fixture_name}.json")
|
|
31
|
-
FileUtils.rm_f(cache_file)
|
|
32
|
-
else
|
|
33
|
-
FileUtils.rm_rf(cache_path)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Generate caches for all fixtures.
|
|
38
|
-
# Each fixture is generated in a transaction that rolls back, so no data persists.
|
|
39
|
-
def generate_all
|
|
40
|
-
Registry.load_definitions
|
|
41
|
-
Registry.fixtures.each { |fixture| generate(fixture.name) }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def generate(fixture_name)
|
|
45
|
-
clear(fixture_name)
|
|
46
|
-
|
|
47
|
-
FixtureKit.configuration.generator.run do
|
|
48
|
-
Runner.run(fixture_name, force: true)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
attr_reader :records, :exposed
|
|
54
|
-
|
|
55
|
-
def initialize(fixture_name)
|
|
56
|
-
@fixture_name = fixture_name
|
|
57
|
-
@records = {}
|
|
58
|
-
@exposed = {}
|
|
14
|
+
def initialize(fixture, definition)
|
|
15
|
+
@fixture = fixture
|
|
16
|
+
@definition = definition
|
|
59
17
|
end
|
|
60
18
|
|
|
61
|
-
def
|
|
62
|
-
cache_path
|
|
63
|
-
File.join(cache_path, "#{@fixture_name}.json")
|
|
19
|
+
def path
|
|
20
|
+
File.join(configuration.cache_path, "#{fixture.name}.json")
|
|
64
21
|
end
|
|
65
22
|
|
|
66
23
|
def exists?
|
|
67
|
-
|
|
68
|
-
self.class.memory_cache.key?(@fixture_name) || File.exist?(cache_file_path)
|
|
24
|
+
@data || File.exist?(path)
|
|
69
25
|
end
|
|
70
26
|
|
|
71
27
|
def load
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
data = self.class.memory_cache[@fixture_name]
|
|
75
|
-
@records = data.fetch("records")
|
|
76
|
-
@exposed = data.fetch("exposed")
|
|
77
|
-
return true
|
|
28
|
+
unless exists?
|
|
29
|
+
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{fixture.name}'"
|
|
78
30
|
end
|
|
79
31
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
32
|
+
@data ||= JSON.parse(File.read(path))
|
|
33
|
+
@data.fetch("records").each do |model_name, sql|
|
|
34
|
+
model = ActiveSupport::Inflector.constantize(model_name)
|
|
35
|
+
connection = model.connection
|
|
36
|
+
connection.disable_referential_integrity do
|
|
37
|
+
# execute_batch is private in current supported Rails versions.
|
|
38
|
+
# This should be revisited when Rails 8.2 makes it public.
|
|
39
|
+
connection.__send__(:execute_batch, [build_delete_sql(model), sql].compact, "FixtureKit Load")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
89
42
|
|
|
90
|
-
|
|
43
|
+
build_repository(@data.fetch("exposed"))
|
|
91
44
|
end
|
|
92
45
|
|
|
93
|
-
def save
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
data = {
|
|
100
|
-
"records" => @records,
|
|
101
|
-
"exposed" => @exposed
|
|
102
|
-
}
|
|
46
|
+
def save
|
|
47
|
+
FixtureKit.runner.isolator.run do
|
|
48
|
+
models = SqlSubscriber.capture do
|
|
49
|
+
@definition.evaluate
|
|
50
|
+
end
|
|
103
51
|
|
|
104
|
-
|
|
105
|
-
|
|
52
|
+
@data = {
|
|
53
|
+
"records" => generate_statements(models),
|
|
54
|
+
"exposed" => build_exposed_mapping(@definition.exposed)
|
|
55
|
+
}
|
|
56
|
+
end
|
|
106
57
|
|
|
107
|
-
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
59
|
+
File.write(path, JSON.pretty_generate(@data))
|
|
108
60
|
end
|
|
109
61
|
|
|
110
62
|
# Query exposed records from the database and return a Repository.
|
|
111
|
-
def build_repository
|
|
112
|
-
exposed_records =
|
|
63
|
+
def build_repository(exposed)
|
|
64
|
+
exposed_records = exposed.each_with_object({}) do |(name, value), hash|
|
|
113
65
|
was_array = value.is_a?(Array)
|
|
114
66
|
records = Array.wrap(value).map { |record_info| find_exposed_record(record_info.fetch("model"), record_info.fetch("id"), name) }
|
|
115
67
|
hash[name.to_sym] = was_array ? records : records.first
|
|
@@ -125,54 +77,47 @@ module FixtureKit
|
|
|
125
77
|
model.find(id)
|
|
126
78
|
rescue ActiveRecord::RecordNotFound
|
|
127
79
|
raise FixtureKit::ExposedRecordNotFound,
|
|
128
|
-
"Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@
|
|
80
|
+
"Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture.name}'"
|
|
129
81
|
end
|
|
130
82
|
|
|
131
|
-
def generate_statements(
|
|
83
|
+
def generate_statements(models)
|
|
132
84
|
statements_by_model = {}
|
|
133
85
|
|
|
134
|
-
|
|
86
|
+
models.each do |model|
|
|
135
87
|
columns = model.column_names
|
|
136
88
|
|
|
137
89
|
rows = []
|
|
138
90
|
model.order(:id).find_each do |record|
|
|
139
91
|
row_values = columns.map do |col|
|
|
140
92
|
value = record.read_attribute_before_type_cast(col)
|
|
141
|
-
connection.quote(value)
|
|
93
|
+
model.connection.quote(value)
|
|
142
94
|
end
|
|
143
95
|
rows << "(#{row_values.join(", ")})"
|
|
144
96
|
end
|
|
145
97
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
sql = build_insert_sql(model.table_name, columns, rows, connection)
|
|
98
|
+
sql = rows.empty? ? nil : build_insert_sql(model.table_name, columns, rows, model.connection)
|
|
149
99
|
statements_by_model[model.name] = sql
|
|
150
100
|
end
|
|
151
101
|
|
|
152
102
|
statements_by_model
|
|
153
103
|
end
|
|
154
104
|
|
|
105
|
+
def build_delete_sql(model)
|
|
106
|
+
"DELETE FROM #{model.quoted_table_name}"
|
|
107
|
+
end
|
|
108
|
+
|
|
155
109
|
def build_insert_sql(table_name, columns, rows, connection)
|
|
156
110
|
quoted_table = connection.quote_table_name(table_name)
|
|
157
111
|
quoted_columns = columns.map { |c| connection.quote_column_name(c) }
|
|
158
112
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
add_conflict_handling(sql, connection)
|
|
113
|
+
"INSERT INTO #{quoted_table} (#{quoted_columns.join(", ")}) VALUES #{rows.join(", ")}"
|
|
162
114
|
end
|
|
163
115
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
sql.sub(/\AINSERT INTO/i, "INSERT OR IGNORE INTO")
|
|
170
|
-
when /postgresql/, /postgis/
|
|
171
|
-
"#{sql} ON CONFLICT DO NOTHING"
|
|
172
|
-
when /mysql/, /trilogy/
|
|
173
|
-
sql.sub(/\AINSERT INTO/i, "INSERT IGNORE INTO")
|
|
174
|
-
else
|
|
175
|
-
sql
|
|
116
|
+
def build_exposed_mapping(exposed)
|
|
117
|
+
exposed.each_with_object({}) do |(name, value), hash|
|
|
118
|
+
was_array = value.is_a?(Array)
|
|
119
|
+
records = Array.wrap(value).map { |record| { "model" => record.class.name, "id" => record.id } }
|
|
120
|
+
hash[name] = was_array ? records : records.first
|
|
176
121
|
end
|
|
177
122
|
end
|
|
178
123
|
end
|
|
@@ -4,18 +4,18 @@ module FixtureKit
|
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_writer :fixture_path
|
|
6
6
|
attr_writer :cache_path
|
|
7
|
-
attr_accessor :
|
|
8
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :isolator
|
|
8
|
+
attr_accessor :on_cache
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
|
-
@fixture_path =
|
|
11
|
+
@fixture_path = "fixture_kit"
|
|
12
12
|
@cache_path = nil
|
|
13
|
-
@
|
|
14
|
-
@
|
|
13
|
+
@isolator = FixtureKit::MinitestIsolator
|
|
14
|
+
@on_cache = nil
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def fixture_path
|
|
18
|
-
@fixture_path
|
|
18
|
+
@fixture_path
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def cache_path
|
|
@@ -24,20 +24,6 @@ module FixtureKit
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
-
def detect_fixture_path
|
|
28
|
-
if defined?(RSpec)
|
|
29
|
-
"spec/fixture_kit"
|
|
30
|
-
elsif defined?(Minitest)
|
|
31
|
-
"test/fixture_kit"
|
|
32
|
-
elsif Dir.exist?("spec")
|
|
33
|
-
"spec/fixture_kit"
|
|
34
|
-
elsif Dir.exist?("test")
|
|
35
|
-
"test/fixture_kit"
|
|
36
|
-
else
|
|
37
|
-
"spec/fixture_kit"
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
27
|
def detect_cache_path
|
|
42
28
|
"tmp/cache/fixture_kit"
|
|
43
29
|
end
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module FixtureKit
|
|
4
|
-
class
|
|
4
|
+
class Definition
|
|
5
5
|
attr_reader :exposed
|
|
6
6
|
|
|
7
|
-
def initialize
|
|
7
|
+
def initialize(&definition)
|
|
8
|
+
@definition = definition
|
|
8
9
|
@exposed = {}
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
def evaluate
|
|
13
|
+
instance_eval(&@definition)
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def expose(**records)
|
|
12
17
|
records.each do |name, record|
|
|
13
|
-
name = name.to_sym
|
|
14
|
-
|
|
15
18
|
if @exposed.key?(name)
|
|
16
|
-
raise FixtureKit::DuplicateNameError,
|
|
17
|
-
Duplicate expose name :#{name}
|
|
18
|
-
|
|
19
|
-
A record with this name has already been exposed in this fixture.
|
|
20
|
-
ERROR
|
|
19
|
+
raise FixtureKit::DuplicateNameError, "Name #{name} already exposed"
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
@exposed[name] = record
|
data/lib/fixture_kit/fixture.rb
CHANGED
|
@@ -2,17 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
module FixtureKit
|
|
4
4
|
class Fixture
|
|
5
|
-
|
|
5
|
+
include ConfigurationHelper
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
attr_reader :name, :path
|
|
8
|
+
|
|
9
|
+
def initialize(name, path)
|
|
8
10
|
@name = name
|
|
9
|
-
@
|
|
11
|
+
@path = path
|
|
12
|
+
@cache = Cache.new(self, definition)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cache(force: false)
|
|
16
|
+
return if @cache.exists? && !force
|
|
17
|
+
|
|
18
|
+
configuration.on_cache&.call(name)
|
|
19
|
+
@cache.save
|
|
10
20
|
end
|
|
11
21
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
def mount
|
|
23
|
+
unless @cache.exists?
|
|
24
|
+
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{name}'"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@cache.load
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def definition
|
|
33
|
+
@definition ||= begin
|
|
34
|
+
definition = eval(File.read(@path), TOPLEVEL_BINDING.dup, @path)
|
|
35
|
+
raise FixtureKit::FixtureDefinitionNotFound, "Could not find fixture definition at '#{@path}'" unless definition.is_a?(Definition)
|
|
36
|
+
definition
|
|
37
|
+
end
|
|
16
38
|
end
|
|
17
39
|
end
|
|
18
40
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/test_case"
|
|
4
|
+
require "active_record/fixtures"
|
|
5
|
+
|
|
6
|
+
module FixtureKit
|
|
7
|
+
class MinitestIsolator < FixtureKit::Isolator
|
|
8
|
+
TEST_NAME = "fixture kit cache pregeneration"
|
|
9
|
+
|
|
10
|
+
def run(&block)
|
|
11
|
+
test_class = build_test_class
|
|
12
|
+
test_method = test_class.test(TEST_NAME) do
|
|
13
|
+
block.call
|
|
14
|
+
pass
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
result = test_class.new(test_method).run
|
|
18
|
+
return if result.passed?
|
|
19
|
+
|
|
20
|
+
raise result.failures.first.error
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def build_test_class
|
|
26
|
+
Class.new(ActiveSupport::TestCase) do
|
|
27
|
+
::Minitest::Runnable.runnables.delete(self)
|
|
28
|
+
include(::ActiveRecord::TestFixtures)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureKit
|
|
4
|
+
class RSpecIsolator < FixtureKit::Isolator
|
|
5
|
+
def run(&block)
|
|
6
|
+
previous_example = ::RSpec.current_example
|
|
7
|
+
previous_scope = ::RSpec.current_scope
|
|
8
|
+
example_group = build_example_group
|
|
9
|
+
example = example_group.example { block.call }
|
|
10
|
+
instance = example_group.new
|
|
11
|
+
succeeded =
|
|
12
|
+
begin
|
|
13
|
+
example.run(instance, ::RSpec::Core::NullReporter)
|
|
14
|
+
ensure
|
|
15
|
+
::RSpec.current_example = previous_example
|
|
16
|
+
::RSpec.current_scope = previous_scope
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
raise example.exception unless succeeded
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def build_example_group
|
|
25
|
+
::RSpec::Core::ExampleGroup.subclass(
|
|
26
|
+
::RSpec::Core::ExampleGroup,
|
|
27
|
+
"FixtureKit",
|
|
28
|
+
[],
|
|
29
|
+
[]
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fixture_kit"
|
|
4
|
+
require "active_support/lazy_load_hooks"
|
|
5
|
+
|
|
6
|
+
module FixtureKit
|
|
7
|
+
module Minitest
|
|
8
|
+
DECLARATION_CLASS_ATTRIBUTE = :fixture_kit_declaration
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def fixture(name)
|
|
12
|
+
self.fixture_kit_declaration = FixtureKit.runner.register(name)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module InstanceMethods
|
|
17
|
+
def fixture
|
|
18
|
+
@_fixture_kit_repository || raise("No fixture declared for this test class. Use `fixture \"name\"` in your test class.")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.configure!(test_case)
|
|
23
|
+
FixtureKit.runner.configuration.fixture_path = "test/fixture_kit"
|
|
24
|
+
FixtureKit.runner.configuration.isolator = FixtureKit::MinitestIsolator
|
|
25
|
+
|
|
26
|
+
test_case.class_attribute DECLARATION_CLASS_ATTRIBUTE, instance_accessor: false
|
|
27
|
+
test_case.extend ClassMethods
|
|
28
|
+
test_case.include InstanceMethods
|
|
29
|
+
|
|
30
|
+
test_case.setup do
|
|
31
|
+
declaration = self.class.fixture_kit_declaration
|
|
32
|
+
next unless declaration
|
|
33
|
+
|
|
34
|
+
FixtureKit.runner.start unless FixtureKit.runner.started?
|
|
35
|
+
@_fixture_kit_repository = declaration.mount
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
|
42
|
+
FixtureKit::Minitest.configure!(self)
|
|
43
|
+
end
|
data/lib/fixture_kit/registry.rb
CHANGED
|
@@ -1,58 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Registry
|
|
5
|
-
class << self
|
|
6
|
-
def fetch(name)
|
|
7
|
-
fixture = find(name)
|
|
8
|
-
return fixture if fixture
|
|
9
|
-
|
|
10
|
-
file_path = fixture_file_path(name)
|
|
11
|
-
unless File.file?(file_path)
|
|
12
|
-
raise FixtureKit::FixtureDefinitionNotFound,
|
|
13
|
-
"Could not find fixture definition file for '#{name}' at '#{file_path}'"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
load file_path
|
|
17
|
-
find(name)
|
|
18
|
-
end
|
|
3
|
+
require "pathname"
|
|
19
4
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
module FixtureKit
|
|
6
|
+
class Registry
|
|
7
|
+
include ConfigurationHelper
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
9
|
+
def initialize
|
|
10
|
+
@registry = {}
|
|
11
|
+
end
|
|
27
12
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
13
|
+
def add(name)
|
|
14
|
+
return @registry[name] if @registry.key?(name)
|
|
31
15
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
fixture_path = FixtureKit.configuration.fixture_path
|
|
37
|
-
Dir.glob(File.join(fixture_path, "**/*.rb")).each do |file|
|
|
38
|
-
load file
|
|
39
|
-
end
|
|
16
|
+
file_path = fixture_file_path(name)
|
|
17
|
+
unless File.file?(file_path)
|
|
18
|
+
raise FixtureKit::FixtureDefinitionNotFound,
|
|
19
|
+
"Could not find fixture definition file for '#{name}' at '#{file_path}'"
|
|
40
20
|
end
|
|
41
21
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
22
|
+
@registry[name] = Fixture.new(name, file_path)
|
|
23
|
+
end
|
|
45
24
|
|
|
46
|
-
|
|
25
|
+
def fixtures
|
|
26
|
+
@registry.values
|
|
27
|
+
end
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
fixture_path = FixtureKit.configuration.fixture_path
|
|
50
|
-
File.expand_path(File.join(fixture_path, "#{name}.rb"))
|
|
51
|
-
end
|
|
29
|
+
private
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
31
|
+
def fixture_file_path(name)
|
|
32
|
+
File.expand_path(File.join(configuration.fixture_path, "#{name}.rb"))
|
|
56
33
|
end
|
|
57
34
|
end
|
|
58
35
|
end
|
data/lib/fixture_kit/rspec.rb
CHANGED
|
@@ -4,11 +4,7 @@ require "fixture_kit"
|
|
|
4
4
|
|
|
5
5
|
module FixtureKit
|
|
6
6
|
module RSpec
|
|
7
|
-
autoload :Declaration, File.expand_path("rspec/declaration", __dir__)
|
|
8
|
-
autoload :Generator, File.expand_path("rspec/generator", __dir__)
|
|
9
|
-
|
|
10
7
|
DECLARATION_METADATA_KEY = :fixture_kit_declaration
|
|
11
|
-
PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
|
|
12
8
|
|
|
13
9
|
# Class methods (extended via config.extend)
|
|
14
10
|
module ClassMethods
|
|
@@ -32,7 +28,7 @@ module FixtureKit
|
|
|
32
28
|
# end
|
|
33
29
|
# end
|
|
34
30
|
def fixture(name)
|
|
35
|
-
metadata[DECLARATION_METADATA_KEY] =
|
|
31
|
+
metadata[DECLARATION_METADATA_KEY] = ::RSpec.configuration.fixture_kit.register(name)
|
|
36
32
|
end
|
|
37
33
|
end
|
|
38
34
|
|
|
@@ -41,46 +37,28 @@ module FixtureKit
|
|
|
41
37
|
# Returns the Repository for the current example's fixture.
|
|
42
38
|
# Access exposed records as methods: fixture.alice, fixture.posts
|
|
43
39
|
def fixture
|
|
44
|
-
@
|
|
40
|
+
@_fixture_kit_repository || raise("No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block.")
|
|
45
41
|
end
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
def self.configure!(config)
|
|
49
|
-
FixtureKit.configuration.
|
|
45
|
+
FixtureKit.runner.configuration.fixture_path = "spec/fixture_kit"
|
|
46
|
+
config.add_setting(:fixture_kit, default: FixtureKit.runner)
|
|
47
|
+
FixtureKit.runner.configuration.isolator = FixtureKit::RSpecIsolator
|
|
48
|
+
|
|
50
49
|
config.extend ClassMethods
|
|
51
|
-
config.include InstanceMethods
|
|
50
|
+
config.include InstanceMethods, DECLARATION_METADATA_KEY
|
|
52
51
|
|
|
53
52
|
# Load declared fixtures at the beginning of each example.
|
|
54
53
|
# Runs inside transactional fixtures and before user-defined before hooks.
|
|
55
54
|
config.prepend_before(:example, DECLARATION_METADATA_KEY) do |example|
|
|
56
|
-
|
|
57
|
-
@_fixture_kit_fixture_set = declaration.fixture_set
|
|
55
|
+
@_fixture_kit_repository = example.metadata[DECLARATION_METADATA_KEY].mount
|
|
58
56
|
end
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
config.when_first_matching_example_defined(DECLARATION_METADATA_KEY) do
|
|
63
|
-
config.before(:suite) do
|
|
64
|
-
if FixtureKit.configuration.autogenerate
|
|
65
|
-
preserve_cache = ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
|
|
66
|
-
Cache.clear unless preserve_cache
|
|
67
|
-
else
|
|
68
|
-
fixture_names_for_loaded_examples.each do |fixture_name|
|
|
69
|
-
Cache.generate(fixture_name)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
58
|
+
config.append_before(:suite) do
|
|
59
|
+
config.fixture_kit.start
|
|
73
60
|
end
|
|
74
61
|
end
|
|
75
|
-
|
|
76
|
-
def self.fixture_names_for_loaded_examples
|
|
77
|
-
::RSpec.world.filtered_examples.each_value.with_object(Set.new) do |examples, names|
|
|
78
|
-
examples.each do |example|
|
|
79
|
-
declaration = example.metadata[DECLARATION_METADATA_KEY]
|
|
80
|
-
names << declaration.name if declaration
|
|
81
|
-
end
|
|
82
|
-
end.to_a
|
|
83
|
-
end
|
|
84
62
|
end
|
|
85
63
|
end
|
|
86
64
|
|
data/lib/fixture_kit/runner.rb
CHANGED
|
@@ -1,93 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module FixtureKit
|
|
6
6
|
class Runner
|
|
7
|
-
|
|
8
|
-
new(fixture_name).run(force: force)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def initialize(fixture_name)
|
|
12
|
-
@fixture_name = fixture_name
|
|
13
|
-
@cache = Cache.new(@fixture_name)
|
|
14
|
-
end
|
|
7
|
+
PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
if force
|
|
18
|
-
execute_and_cache
|
|
19
|
-
elsif @cache.exists?
|
|
20
|
-
execute_from_cache
|
|
21
|
-
elsif FixtureKit.configuration.autogenerate
|
|
22
|
-
execute_and_cache
|
|
23
|
-
else
|
|
24
|
-
raise FixtureKit::CacheMissingError, <<~ERROR
|
|
25
|
-
Cache not found for fixture '#{@fixture_name}'.
|
|
9
|
+
attr_reader :configuration, :registry
|
|
26
10
|
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
def initialize
|
|
12
|
+
@configuration = Configuration.new
|
|
13
|
+
@registry = Registry.new
|
|
14
|
+
@isolator = nil
|
|
15
|
+
@started = false
|
|
16
|
+
end
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
def register(name)
|
|
19
|
+
registry.add(name).tap do |fixture|
|
|
20
|
+
fixture.cache if started?
|
|
32
21
|
end
|
|
33
22
|
end
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
fixture = Registry.fetch(@fixture_name)
|
|
39
|
-
|
|
40
|
-
# Start capturing SQL
|
|
41
|
-
capture = SqlCapture.new
|
|
42
|
-
capture.start
|
|
43
|
-
|
|
44
|
-
# Execute fixture definition - returns exposed records hash
|
|
45
|
-
exposed = fixture.execute
|
|
46
|
-
|
|
47
|
-
# Stop capturing and get affected models with their connections
|
|
48
|
-
models_with_connections = capture.stop
|
|
24
|
+
def start
|
|
25
|
+
raise RunnerAlreadyStartedError, "FixtureKit::Runner has already been started" if started?
|
|
26
|
+
@started = true
|
|
49
27
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
models_with_connections: models_with_connections,
|
|
53
|
-
exposed_mapping: build_exposed_mapping(exposed)
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
# Return Repository from the exposed records
|
|
57
|
-
Repository.new(exposed)
|
|
28
|
+
clear_cache unless preserve_cache?
|
|
29
|
+
registry.fixtures.each(&:cache)
|
|
58
30
|
end
|
|
59
31
|
|
|
60
|
-
def
|
|
61
|
-
@
|
|
62
|
-
|
|
63
|
-
# Execute cached SQL statements by model
|
|
64
|
-
@cache.records.each do |model_name, sql|
|
|
65
|
-
next if sql.nil? || sql.empty?
|
|
66
|
-
|
|
67
|
-
model = ActiveSupport::Inflector.constantize(model_name)
|
|
68
|
-
connection = model.connection
|
|
69
|
-
connection.execute(sql)
|
|
70
|
-
end
|
|
32
|
+
def isolator
|
|
33
|
+
@isolator ||= configuration.isolator.new
|
|
34
|
+
end
|
|
71
35
|
|
|
72
|
-
|
|
73
|
-
@
|
|
36
|
+
def started?
|
|
37
|
+
@started
|
|
74
38
|
end
|
|
75
39
|
|
|
76
|
-
|
|
77
|
-
mapping = {}
|
|
40
|
+
private
|
|
78
41
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
{ "model" => record.class.name, "id" => record.id }
|
|
83
|
-
end
|
|
84
|
-
else
|
|
85
|
-
record = record_or_records
|
|
86
|
-
mapping[name.to_s] = { "model" => record.class.name, "id" => record.id }
|
|
87
|
-
end
|
|
88
|
-
end
|
|
42
|
+
def clear_cache
|
|
43
|
+
FileUtils.rm_rf(configuration.cache_path)
|
|
44
|
+
end
|
|
89
45
|
|
|
90
|
-
|
|
46
|
+
def preserve_cache?
|
|
47
|
+
ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
|
|
91
48
|
end
|
|
92
49
|
end
|
|
93
50
|
end
|
|
@@ -1,35 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "pathname"
|
|
4
|
-
|
|
5
3
|
module FixtureKit
|
|
6
4
|
module Singleton
|
|
7
5
|
def configure
|
|
8
|
-
yield(configuration) if block_given?
|
|
6
|
+
yield(runner.configuration) if block_given?
|
|
9
7
|
self
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
def
|
|
13
|
-
@
|
|
10
|
+
def runner
|
|
11
|
+
@runner ||= Runner.new
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def define(&block)
|
|
17
|
-
|
|
18
|
-
fixture_path = File.expand_path(configuration.fixture_path)
|
|
19
|
-
|
|
20
|
-
# "/abs/path/spec/fixtures/teams/basic.rb" -> "teams/basic"
|
|
21
|
-
relative_path = Pathname.new(caller_file).relative_path_from(Pathname.new(fixture_path))
|
|
22
|
-
name = relative_path.to_s.sub(/\.rb$/, "")
|
|
23
|
-
|
|
24
|
-
fixture = Fixture.new(name, &block)
|
|
25
|
-
Registry.register(fixture)
|
|
26
|
-
fixture
|
|
15
|
+
Definition.new(&block)
|
|
27
16
|
end
|
|
28
17
|
|
|
29
18
|
def reset
|
|
30
|
-
@
|
|
31
|
-
Registry.reset
|
|
32
|
-
Cache.clear_memory_cache
|
|
19
|
+
@runner = nil
|
|
33
20
|
end
|
|
34
21
|
end
|
|
35
22
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/notifications"
|
|
4
|
+
require "active_support/inflector"
|
|
5
|
+
|
|
6
|
+
module FixtureKit
|
|
7
|
+
class SqlSubscriber
|
|
8
|
+
EVENT = "sql.active_record"
|
|
9
|
+
NAME_PATTERN = /\A(?<model_name>.+?) (?:(?:Bulk )?(?:Insert|Upsert)|Create|Destroy|(?:Update|Delete)(?: All)?)\z/
|
|
10
|
+
|
|
11
|
+
def self.capture(&block)
|
|
12
|
+
models = Set.new
|
|
13
|
+
subscriber = lambda do |_event_name, _start, _finish, _id, payload|
|
|
14
|
+
name = payload[:name].to_s
|
|
15
|
+
model_name = name[NAME_PATTERN, :model_name]
|
|
16
|
+
next unless model_name
|
|
17
|
+
|
|
18
|
+
models.add(ActiveSupport::Inflector.constantize(model_name))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ActiveSupport::Notifications.subscribed(subscriber, EVENT, monotonic: true, &block)
|
|
22
|
+
|
|
23
|
+
models.to_a
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/fixture_kit/version.rb
CHANGED
data/lib/fixture_kit.rb
CHANGED
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module FixtureKit
|
|
4
4
|
class Error < StandardError; end
|
|
5
|
-
class DuplicateFixtureError < Error; end
|
|
6
5
|
class DuplicateNameError < Error; end
|
|
7
6
|
class CacheMissingError < Error; end
|
|
8
|
-
class PregenerationError < Error; end
|
|
9
7
|
class FixtureDefinitionNotFound < Error; end
|
|
10
8
|
class ExposedRecordNotFound < Error; end
|
|
9
|
+
class RunnerAlreadyStartedError < Error; end
|
|
11
10
|
|
|
12
|
-
autoload :VERSION,
|
|
13
|
-
autoload :Configuration,
|
|
14
|
-
autoload :
|
|
15
|
-
autoload :
|
|
16
|
-
autoload :
|
|
17
|
-
autoload :
|
|
18
|
-
autoload :
|
|
19
|
-
autoload :
|
|
20
|
-
autoload :
|
|
21
|
-
autoload :
|
|
22
|
-
autoload :
|
|
23
|
-
autoload :
|
|
11
|
+
autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
|
|
12
|
+
autoload :Configuration, File.expand_path("fixture_kit/configuration", __dir__)
|
|
13
|
+
autoload :ConfigurationHelper, File.expand_path("fixture_kit/configuration_helper", __dir__)
|
|
14
|
+
autoload :Singleton, File.expand_path("fixture_kit/singleton", __dir__)
|
|
15
|
+
autoload :Fixture, File.expand_path("fixture_kit/fixture", __dir__)
|
|
16
|
+
autoload :Definition, File.expand_path("fixture_kit/definition", __dir__)
|
|
17
|
+
autoload :Registry, File.expand_path("fixture_kit/registry", __dir__)
|
|
18
|
+
autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
|
|
19
|
+
autoload :SqlSubscriber, File.expand_path("fixture_kit/sql_subscriber", __dir__)
|
|
20
|
+
autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
|
|
21
|
+
autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
|
|
22
|
+
autoload :Isolator, File.expand_path("fixture_kit/isolator", __dir__)
|
|
23
|
+
autoload :MinitestIsolator, File.expand_path("fixture_kit/isolators/minitest_isolator", __dir__)
|
|
24
|
+
autoload :RSpecIsolator, File.expand_path("fixture_kit/isolators/rspec_isolator", __dir__)
|
|
24
25
|
|
|
25
26
|
extend Singleton
|
|
26
27
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fixture_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ngan Pham
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -136,19 +136,19 @@ files:
|
|
|
136
136
|
- lib/fixture_kit.rb
|
|
137
137
|
- lib/fixture_kit/cache.rb
|
|
138
138
|
- lib/fixture_kit/configuration.rb
|
|
139
|
-
- lib/fixture_kit/
|
|
139
|
+
- lib/fixture_kit/configuration_helper.rb
|
|
140
|
+
- lib/fixture_kit/definition.rb
|
|
140
141
|
- lib/fixture_kit/fixture.rb
|
|
141
|
-
- lib/fixture_kit/
|
|
142
|
+
- lib/fixture_kit/isolator.rb
|
|
143
|
+
- lib/fixture_kit/isolators/minitest_isolator.rb
|
|
144
|
+
- lib/fixture_kit/isolators/rspec_isolator.rb
|
|
145
|
+
- lib/fixture_kit/minitest.rb
|
|
142
146
|
- lib/fixture_kit/registry.rb
|
|
143
147
|
- lib/fixture_kit/repository.rb
|
|
144
148
|
- lib/fixture_kit/rspec.rb
|
|
145
|
-
- lib/fixture_kit/rspec/declaration.rb
|
|
146
|
-
- lib/fixture_kit/rspec/generator.rb
|
|
147
149
|
- lib/fixture_kit/runner.rb
|
|
148
150
|
- lib/fixture_kit/singleton.rb
|
|
149
|
-
- lib/fixture_kit/
|
|
150
|
-
- lib/fixture_kit/test_case.rb
|
|
151
|
-
- lib/fixture_kit/test_case/generator.rb
|
|
151
|
+
- lib/fixture_kit/sql_subscriber.rb
|
|
152
152
|
- lib/fixture_kit/version.rb
|
|
153
153
|
homepage: https://github.com/Gusto/fixture_kit
|
|
154
154
|
licenses:
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FixtureKit
|
|
4
|
-
module RSpec
|
|
5
|
-
class Generator < FixtureKit::Generator
|
|
6
|
-
def run(&block)
|
|
7
|
-
previous_example = ::RSpec.current_example
|
|
8
|
-
previous_scope = ::RSpec.current_scope
|
|
9
|
-
example_group = build_example_group
|
|
10
|
-
example = build_example(example_group, &block)
|
|
11
|
-
instance = example_group.new(example.inspect_output)
|
|
12
|
-
succeeded =
|
|
13
|
-
begin
|
|
14
|
-
example.run(instance, ::RSpec::Core::NullReporter)
|
|
15
|
-
ensure
|
|
16
|
-
example_group.remove_example(example)
|
|
17
|
-
::RSpec.current_example = previous_example
|
|
18
|
-
::RSpec.current_scope = previous_scope
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
unless succeeded
|
|
22
|
-
raise example.exception if example.exception
|
|
23
|
-
raise FixtureKit::PregenerationError, "FixtureKit pregeneration failed"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def build_example(example_group, &block)
|
|
30
|
-
example_group.example(
|
|
31
|
-
"FixtureKit cache pregeneration"
|
|
32
|
-
) { block.call }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def build_example_group
|
|
36
|
-
::RSpec::Core::ExampleGroup.subclass(
|
|
37
|
-
::RSpec::Core::ExampleGroup,
|
|
38
|
-
"FixtureKit::RSpec::Generator",
|
|
39
|
-
[],
|
|
40
|
-
[]
|
|
41
|
-
)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_support/notifications"
|
|
4
|
-
require "active_support/inflector"
|
|
5
|
-
|
|
6
|
-
module FixtureKit
|
|
7
|
-
class SqlCapture
|
|
8
|
-
SQL_EVENT = "sql.active_record"
|
|
9
|
-
|
|
10
|
-
def initialize
|
|
11
|
-
@models = {} # { Model => connection }
|
|
12
|
-
@subscription = nil
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def start
|
|
16
|
-
@subscription = ActiveSupport::Notifications.subscribe(SQL_EVENT) do |*, payload|
|
|
17
|
-
next unless payload[:sql] =~ /\AINSERT INTO/i
|
|
18
|
-
|
|
19
|
-
# payload[:name] is like "User Create" - extract model name
|
|
20
|
-
name = payload[:name]
|
|
21
|
-
next unless name&.end_with?(" Create")
|
|
22
|
-
|
|
23
|
-
model_name = name.sub(/ Create\z/, "")
|
|
24
|
-
model = ActiveSupport::Inflector.constantize(model_name)
|
|
25
|
-
@models[model] ||= payload[:connection]
|
|
26
|
-
rescue NameError
|
|
27
|
-
# Skip if model can't be found
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def stop
|
|
32
|
-
ActiveSupport::Notifications.unsubscribe(@subscription) if @subscription
|
|
33
|
-
@subscription = nil
|
|
34
|
-
@models
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_support/test_case"
|
|
4
|
-
require "active_record/fixtures"
|
|
5
|
-
|
|
6
|
-
module FixtureKit
|
|
7
|
-
module TestCase
|
|
8
|
-
class Generator < FixtureKit::Generator
|
|
9
|
-
TEST_METHOD_NAME = "test_fixture_kit_cache_pregeneration"
|
|
10
|
-
|
|
11
|
-
def run(&block)
|
|
12
|
-
result = build_test_class(&block).run
|
|
13
|
-
return if result.passed?
|
|
14
|
-
|
|
15
|
-
failure = result.failures.first
|
|
16
|
-
raise failure.error if failure.respond_to?(:error)
|
|
17
|
-
raise failure if failure
|
|
18
|
-
|
|
19
|
-
raise FixtureKit::PregenerationError, "FixtureKit pregeneration failed"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def build_test_class(&block)
|
|
25
|
-
Class.new(ActiveSupport::TestCase) do
|
|
26
|
-
::Minitest::Runnable.runnables.delete(self)
|
|
27
|
-
include(::ActiveRecord::TestFixtures)
|
|
28
|
-
|
|
29
|
-
define_method(TEST_METHOD_NAME) do
|
|
30
|
-
block.call
|
|
31
|
-
pass
|
|
32
|
-
end
|
|
33
|
-
end.new(TEST_METHOD_NAME)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|