fixture_kit 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41dfc0a3fb06edb02068d8127c89708ec7e2c5792b40fb031ee553716a7c54ca
4
- data.tar.gz: 4193bec6652924d99a7f564212087afa7f678b1f7995fca67f883dcfb1a45d24
3
+ metadata.gz: 6ccc3496f3e23da0877bac37e98d2fd325daf737f4d7bc2988d80156d202c248
4
+ data.tar.gz: 332a5f7824ec4c449ef052bf67616c71b7bca07749f69dcb9c1369afbb95f1a4
5
5
  SHA512:
6
- metadata.gz: e9d7108cd6bda22319c02ea76384772210bdbea00f3ac93c6f6263dd72f5d7475f4cb0087f57f2d316dd17bc2fe36ad3597e87a417dae8149b0404f9196e6a0f
7
- data.tar.gz: 25c72f2db8d0c93e06e244acc12d224ec36a0cbb85cbbd65c9a4a9d92268b76847c9fb0150508f78d2a1446a7c66587b3c5d479b701c2f072d97e6df92ec289d
6
+ metadata.gz: aaab87039e3f040b6b1f200a2e853e46263449f2bc900f664d167b009adffb2eccd48fc9080051b67dc6ad8c1e12f3aca4796db45ced24f63ad35f671cddb7f6
7
+ data.tar.gz: 55d185c158111d9a892ceb558af729f48a2708c4975e1365a411bdbbb6a6e26d776fd511bc9505dc818aa4e4747062acb5b51b514c3efe43351ff342e036a3e9
data/README.md CHANGED
@@ -89,6 +89,26 @@ end
89
89
  - ActiveRecord >= 8.0
90
90
  - ActiveSupport >= 8.0
91
91
 
92
+ ## Releasing
93
+
94
+ 1. Bump the version in `lib/fixture_kit/version.rb`
95
+ 2. Update lockfiles:
96
+ ```sh
97
+ bundle install
98
+ cd spec/dummy && bundle install && cd ../..
99
+ bundle exec appraisal install
100
+ ```
101
+ 3. Commit and push to main:
102
+ ```sh
103
+ git add lib/fixture_kit/version.rb spec/dummy/Gemfile.lock gemfiles/*.gemfile.lock
104
+ git commit -m "Release vX.Y.Z"
105
+ git push
106
+ ```
107
+ 4. Create a GitHub release:
108
+ ```sh
109
+ gh release create vX.Y.Z --title "vX.Y.Z" --target main --generate-notes
110
+ ```
111
+
92
112
  ## License
93
113
 
94
114
  MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require "set"
5
+
6
+ module FixtureKit
7
+ module Analyzer
8
+ class AstFactoryDetector
9
+ FACTORY_METHODS = %w[create build build_stubbed create_list build_list].to_set.freeze
10
+
11
+ def initialize
12
+ @file_cache = {}
13
+ end
14
+
15
+ # Returns array of factory name strings, e.g. ["company", "employee"]
16
+ def detect(mod, method_name)
17
+ meth = mod.instance_method(method_name)
18
+ file, line = meth.source_location
19
+ return [] unless file && File.exist?(file)
20
+
21
+ ast = parse_file(file)
22
+ block = find_block_at_line(ast, line)
23
+ return [] unless block
24
+
25
+ collect_factory_calls(block)
26
+ rescue
27
+ []
28
+ end
29
+
30
+ private
31
+
32
+ def parse_file(path)
33
+ @file_cache[path] ||= Prism.parse(File.read(path)).value
34
+ end
35
+
36
+ # Find the block node that starts at the target line.
37
+ def find_block_at_line(node, target_line)
38
+ if node.is_a?(Prism::BlockNode) && node.location.start_line == target_line
39
+ return node
40
+ end
41
+
42
+ node.child_nodes.compact.each do |child|
43
+ found = find_block_at_line(child, target_line)
44
+ return found if found
45
+ end
46
+
47
+ nil
48
+ end
49
+
50
+ # Walk an AST subtree collecting factory call argument symbols.
51
+ def collect_factory_calls(node)
52
+ results = []
53
+
54
+ if node.is_a?(Prism::CallNode) && FACTORY_METHODS.include?(node.name.to_s)
55
+ first_arg = node.arguments&.arguments&.first
56
+ results << first_arg.value if first_arg.is_a?(Prism::SymbolNode)
57
+ end
58
+
59
+ node.child_nodes.compact.each { |child| results.concat(collect_factory_calls(child)) }
60
+ results
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module Analyzer
5
+ class FileResult
6
+ attr_reader :file, :total_examples, :lets
7
+
8
+ def initialize(file:, total_examples:, lets:)
9
+ @file = file
10
+ @total_examples = total_examples
11
+ @lets = lets.sort_by { |l| -l.example_count }
12
+ end
13
+
14
+ def max_reuse
15
+ lets.first&.example_count || 0
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ file: file,
21
+ total_examples: total_examples,
22
+ max_reuse: max_reuse,
23
+ lets: lets.map(&:to_h),
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module Analyzer
5
+ class GroupAnalyzer
6
+ def initialize(detector: AstFactoryDetector.new)
7
+ @detector = detector
8
+ end
9
+
10
+ # Check if a group directly defines a let with this name
11
+ def group_defines_let?(group, let_name)
12
+ own_mod = group.const_get(:LetDefinitions, false)
13
+ own_mod.instance_methods(false).include?(let_name.to_sym)
14
+ rescue NameError
15
+ false
16
+ end
17
+
18
+ # Count examples that inherit a let defined at `group`, stopping at overrides
19
+ def examples_using_let(group, let_name)
20
+ count = group.examples.count
21
+ group.children.each do |child|
22
+ next if group_defines_let?(child, let_name)
23
+ count += examples_using_let(child, let_name)
24
+ end
25
+ count
26
+ end
27
+
28
+ # Total examples in group + all descendants
29
+ def total_examples(group)
30
+ group.examples.count + group.children.sum { |c| total_examples(c) }
31
+ end
32
+
33
+ # Walk tree, collect factory lets
34
+ def analyze(group, results = [])
35
+ own_mod = begin
36
+ group.const_get(:LetDefinitions, false)
37
+ rescue NameError
38
+ nil
39
+ end
40
+
41
+ if own_mod
42
+ own_mod.instance_methods(false).each do |method_name|
43
+ factories = @detector.detect(own_mod, method_name)
44
+ next if factories.empty?
45
+
46
+ loc = own_mod.instance_method(method_name).source_location
47
+ example_count = examples_using_let(group, method_name.to_s)
48
+
49
+ results << LetDefinition.new(
50
+ name: method_name.to_s,
51
+ factories: factories,
52
+ example_count: example_count,
53
+ file: loc&.first,
54
+ line: loc&.last,
55
+ group_description: group.description.to_s[0, 80],
56
+ )
57
+ end
58
+ end
59
+
60
+ group.children.each { |child| analyze(child, results) }
61
+ results
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module Analyzer
5
+ class LetDefinition
6
+ attr_reader :name, :factories, :example_count, :file, :line, :group_description
7
+
8
+ def initialize(name:, factories:, example_count:, file:, line:, group_description:)
9
+ @name = name
10
+ @factories = factories
11
+ @example_count = example_count
12
+ @file = file
13
+ @line = line
14
+ @group_description = group_description
15
+ end
16
+
17
+ def defined_at
18
+ "#{file}:#{line}"
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ let_name: name,
24
+ factories: factories,
25
+ example_count: example_count,
26
+ defined_at: defined_at,
27
+ group: group_description,
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module FixtureKit
6
+ module Analyzer
7
+ class Runner
8
+ def initialize(
9
+ format: ENV.fetch("ANALYZER_FORMAT", "text"),
10
+ limit: ENV.fetch("ANALYZER_LIMIT", "50").to_i,
11
+ lets_per_file: ENV.fetch("ANALYZER_LETS_PER_FILE", "8").to_i,
12
+ min_reuse: ENV.fetch("ANALYZER_MIN_REUSE", "2").to_i,
13
+ output_path: ENV["ANALYZER_OUTPUT"]
14
+ )
15
+ @format = format
16
+ @limit = limit
17
+ @lets_per_file = lets_per_file
18
+ @min_reuse = min_reuse
19
+ @output_path = output_path
20
+ end
21
+
22
+ def run(example_groups)
23
+ return if example_groups.empty?
24
+
25
+ total = example_groups.length
26
+ $stderr.puts "[fixture_kit:analyzer] Analyzing #{total} top-level example groups..."
27
+
28
+ analyzer = GroupAnalyzer.new
29
+
30
+ by_file = Hash.new { |h, k| h[k] = {lets: [], total_examples: 0} }
31
+
32
+ example_groups.each_with_index do |group, idx|
33
+ file = group.metadata[:file_path] || group.metadata[:absolute_file_path] || "unknown"
34
+ if (idx + 1) % 500 == 0 || idx + 1 == total
35
+ $stderr.puts "[fixture_kit:analyzer] Processing group #{idx + 1}/#{total} (#{file})"
36
+ end
37
+ by_file[file][:total_examples] += analyzer.total_examples(group)
38
+ analyzer.analyze(group, by_file[file][:lets])
39
+ end
40
+
41
+ results = by_file.map do |file, data|
42
+ FileResult.new(file: file, total_examples: data[:total_examples], lets: data[:lets])
43
+ end.sort_by { |r| -r.max_reuse }
44
+
45
+ output(results)
46
+ results
47
+ end
48
+
49
+ # Install the at_exit hook that runs the analyzer after rspec --dry-run
50
+ def self.install_rspec_hook!
51
+ load_start = Time.now
52
+
53
+ progress_thread = Thread.new do
54
+ last_count = 0
55
+ loop do
56
+ sleep 5
57
+ current = ::RSpec.world.example_groups.length rescue 0
58
+ if current != last_count
59
+ $stderr.puts "[fixture_kit:analyzer] Loading... #{current} top-level groups found (#{(Time.now - load_start).round(1)}s)"
60
+ last_count = current
61
+ end
62
+ end
63
+ end
64
+ progress_thread.abort_on_exception = false
65
+
66
+ at_exit do
67
+ progress_thread.kill
68
+ next unless defined?(::RSpec) && ::RSpec.world.example_groups.any?
69
+
70
+ $stderr.puts "[fixture_kit:analyzer] Spec loading complete: #{::RSpec.world.example_groups.length} groups in #{(Time.now - load_start).round(1)}s"
71
+ new.run(::RSpec.world.example_groups)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def output(results)
78
+ case @format
79
+ when "json"
80
+ json = JSON.pretty_generate(results.map(&:to_h))
81
+ $stdout.puts json
82
+ else
83
+ TextFormatter.new(
84
+ limit: @limit,
85
+ lets_per_file: @lets_per_file,
86
+ min_reuse: @min_reuse,
87
+ ).render(results)
88
+ end
89
+
90
+ if @output_path
91
+ File.write(@output_path, JSON.pretty_generate(results.map(&:to_h)))
92
+ $stderr.puts "[fixture_kit:analyzer] Full JSON written to: #{@output_path}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module Analyzer
5
+ class TextFormatter
6
+ def initialize(limit:, lets_per_file:, min_reuse:, io: $stdout)
7
+ @limit = limit
8
+ @lets_per_file = lets_per_file
9
+ @min_reuse = min_reuse
10
+ @io = io
11
+ end
12
+
13
+ def render(results)
14
+ filtered = results.select { |r| r.max_reuse >= @min_reuse }
15
+
16
+ @io.puts
17
+ @io.puts "=" * 110
18
+ @io.puts "FACTORY LET REUSE (ranked by highest single-let example count)"
19
+ @io.puts "=" * 110
20
+ @io.puts
21
+ @io.puts "#{results.length} spec files analyzed, #{filtered.length} with max reuse >= #{@min_reuse}"
22
+
23
+ filtered.first(@limit).each_with_index do |result, idx|
24
+ @io.puts
25
+ @io.puts "#{idx + 1}. #{result.file}"
26
+ @io.puts " examples: #{result.total_examples} | factory lets: #{result.lets.length} | max reuse: #{result.max_reuse}"
27
+ @io.puts " " + "-" * 95
28
+
29
+ result.lets.first(@lets_per_file).each do |l|
30
+ @io.puts Kernel.format(" let(:%s) examples: %d factories: %s",
31
+ l.name, l.example_count, l.factories.join(", "))
32
+ @io.puts " group: #{l.group_description}"
33
+ end
34
+
35
+ remaining = result.lets.length - @lets_per_file
36
+ @io.puts " ... +#{remaining} more" if remaining > 0
37
+ end
38
+
39
+ @io.puts
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FixtureKit::Analyzer — Find high-ROI FixtureKit optimization targets.
4
+ #
5
+ # Analyzes RSpec specs via --dry-run to find factory_bot `let` blocks
6
+ # and rank them by how many examples each `let` is shared across.
7
+ # The more examples share a let, the bigger the speedup from caching
8
+ # it with FixtureKit.
9
+ #
10
+ # Usage:
11
+ # rspec --dry-run --require fixture_kit/analyzer [spec files or dirs...]
12
+ #
13
+ # Options (via env vars):
14
+ # ANALYZER_FORMAT=text|json Output format (default: text)
15
+ # ANALYZER_LIMIT=N Max files to show (default: 50)
16
+ # ANALYZER_LETS_PER_FILE=N Max lets to show per file (default: 8)
17
+ # ANALYZER_MIN_REUSE=N Only show files with max reuse >= N (default: 2)
18
+ # ANALYZER_OUTPUT=path Write JSON to file (default: none)
19
+
20
+ module FixtureKit
21
+ module Analyzer
22
+ autoload :AstFactoryDetector, File.expand_path("analyzer/ast_factory_detector", __dir__)
23
+ autoload :GroupAnalyzer, File.expand_path("analyzer/group_analyzer", __dir__)
24
+ autoload :LetDefinition, File.expand_path("analyzer/let_definition", __dir__)
25
+ autoload :FileResult, File.expand_path("analyzer/file_result", __dir__)
26
+ autoload :TextFormatter, File.expand_path("analyzer/text_formatter", __dir__)
27
+ autoload :Runner, File.expand_path("analyzer/runner", __dir__)
28
+ end
29
+ end
30
+
31
+ # Auto-hook into RSpec when loaded via --require
32
+ if defined?(RSpec)
33
+ FixtureKit::Analyzer::Runner.install_rspec_hook!
34
+ end
@@ -1,14 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "fileutils"
5
3
  require "active_support/core_ext/array/wrap"
6
- require "active_support/inflector"
7
4
 
8
5
  module FixtureKit
9
6
  class Cache
10
7
  ANONYMOUS_DIRECTORY = "_anonymous"
11
- MemoryData = Data.define(:records, :exposed)
12
8
 
13
9
  include ConfigurationHelper
14
10
 
@@ -19,7 +15,7 @@ module FixtureKit
19
15
  end
20
16
 
21
17
  def path
22
- File.join(configuration.cache_path, "#{identifier}.json")
18
+ file_cache.path
23
19
  end
24
20
 
25
21
  def identifier
@@ -34,7 +30,11 @@ module FixtureKit
34
30
  end
35
31
 
36
32
  def exists?
37
- data || File.exist?(path)
33
+ data || file_cache.exists?
34
+ end
35
+
36
+ def clear_memory
37
+ @data = nil
38
38
  end
39
39
 
40
40
  def load
@@ -42,7 +42,7 @@ module FixtureKit
42
42
  raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{fixture.identifier}'"
43
43
  end
44
44
 
45
- @data ||= load_memory_data
45
+ @data ||= file_cache.read
46
46
  statements_by_connection(data.records).each do |connection, statements|
47
47
  connection.disable_referential_integrity do
48
48
  # execute_batch is private in current supported Rails versions.
@@ -64,17 +64,23 @@ module FixtureKit
64
64
  captured_models.concat(fixture.parent.cache.data.records.keys)
65
65
  end
66
66
 
67
- @data = MemoryData.new(
67
+ @data = MemoryCache.new(
68
68
  records: generate_statements(captured_models),
69
- exposed: build_exposed_mapping(fixture.definition.exposed)
69
+ exposed: file_cache.serialize_exposed(fixture.definition.exposed)
70
70
  )
71
71
  end
72
72
 
73
- save_file_data
73
+ file_cache.write(data)
74
74
  end
75
75
 
76
76
  private
77
77
 
78
+ def file_cache
79
+ @file_cache ||= FileCache.new(
80
+ File.join(configuration.cache_path, "#{identifier}.json")
81
+ )
82
+ end
83
+
78
84
  def generate_statements(models)
79
85
  models.uniq.each_with_object({}) do |model, statements|
80
86
  columns = model.column_names
@@ -104,16 +110,6 @@ module FixtureKit
104
110
  "INSERT INTO #{quoted_table} (#{quoted_columns.join(", ")}) VALUES #{rows.join(", ")}"
105
111
  end
106
112
 
107
- def build_exposed_mapping(exposed)
108
- exposed.each_with_object({}) do |(name, record), hash|
109
- if record.is_a?(Array)
110
- hash[name] = record.map { |record| { record.class => record.id } }
111
- else
112
- hash[name] = { record.class => record.id }
113
- end
114
- end
115
- end
116
-
117
113
  def statements_by_connection(records)
118
114
  deleted_tables = Set.new
119
115
 
@@ -129,27 +125,5 @@ module FixtureKit
129
125
  grouped[connection] << sql if sql
130
126
  end
131
127
  end
132
-
133
- def load_memory_data
134
- file_data = JSON.parse(File.read(path))
135
- records = file_data.fetch("records").transform_keys do |model_name|
136
- ActiveSupport::Inflector.constantize(model_name)
137
- end
138
-
139
- exposed = file_data.fetch("exposed").each_with_object({}) do |(name, value), hash|
140
- if value.is_a?(Array)
141
- hash[name.to_sym] = value.map { |r| { ActiveSupport::Inflector.constantize(r.keys.first) => r.values.first } }
142
- else
143
- hash[name.to_sym] = { ActiveSupport::Inflector.constantize(value.keys.first) => value.values.first }
144
- end
145
- end
146
-
147
- MemoryData.new(records: records, exposed: exposed)
148
- end
149
-
150
- def save_file_data
151
- FileUtils.mkdir_p(File.dirname(path))
152
- File.write(path, JSON.pretty_generate(data.to_h))
153
- end
154
128
  end
155
129
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "active_support/inflector"
6
+
7
+ module FixtureKit
8
+ class FileCache
9
+ attr_reader :path
10
+
11
+ def initialize(path)
12
+ @path = path
13
+ end
14
+
15
+ def exists?
16
+ File.exist?(path)
17
+ end
18
+
19
+ def read
20
+ file_data = JSON.parse(File.read(path))
21
+ records = file_data.fetch("records").transform_keys do |model_name|
22
+ ActiveSupport::Inflector.constantize(model_name)
23
+ end
24
+
25
+ exposed = file_data.fetch("exposed").each_with_object({}) do |(name, value), hash|
26
+ if value.is_a?(Array)
27
+ hash[name.to_sym] = value.map { |r| { ActiveSupport::Inflector.constantize(r.keys.first) => r.values.first } }
28
+ else
29
+ hash[name.to_sym] = { ActiveSupport::Inflector.constantize(value.keys.first) => value.values.first }
30
+ end
31
+ end
32
+
33
+ MemoryCache.new(records: records, exposed: exposed)
34
+ end
35
+
36
+ def write(data)
37
+ FileUtils.mkdir_p(File.dirname(path))
38
+ File.write(path, JSON.pretty_generate(data.to_h))
39
+ end
40
+
41
+ def serialize_exposed(exposed)
42
+ exposed.each_with_object({}) do |(name, record), hash|
43
+ if record.is_a?(Array)
44
+ hash[name] = record.map { |record| { record.class => record.id } }
45
+ else
46
+ hash[name] = { record.class => record.id }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -36,8 +36,16 @@ module FixtureKit
36
36
  emit(:cache_mounted) { @cache.load }
37
37
  end
38
38
 
39
+ def finish
40
+ @cache.clear_memory if anonymous?
41
+ end
42
+
39
43
  private
40
44
 
45
+ def anonymous?
46
+ !identifier.is_a?(String)
47
+ end
48
+
41
49
  def emit(event)
42
50
  cache_identifier = @cache.identifier
43
51
  unless block_given?
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ class MemoryCache
5
+ attr_reader :records, :exposed
6
+
7
+ def initialize(records:, exposed:)
8
+ @records = records
9
+ @exposed = exposed
10
+ freeze
11
+ end
12
+
13
+ def to_h
14
+ { records: records, exposed: exposed }
15
+ end
16
+ end
17
+ end
@@ -23,6 +23,8 @@ module FixtureKit
23
23
  end
24
24
 
25
25
  super
26
+
27
+ declaration&.finish
26
28
  end
27
29
  end
28
30
 
@@ -35,6 +35,10 @@ module FixtureKit
35
35
  prepend_before(:context) do
36
36
  self.class.metadata[DECLARATION_METADATA_KEY].generate
37
37
  end
38
+
39
+ append_after(:context) do
40
+ self.class.metadata[DECLARATION_METADATA_KEY].finish
41
+ end
38
42
  end
39
43
  end
40
44
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  end
data/lib/fixture_kit.rb CHANGED
@@ -21,6 +21,8 @@ module FixtureKit
21
21
  autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
22
22
  autoload :SqlSubscriber, File.expand_path("fixture_kit/sql_subscriber", __dir__)
23
23
  autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
24
+ autoload :FileCache, File.expand_path("fixture_kit/file_cache", __dir__)
25
+ autoload :MemoryCache, File.expand_path("fixture_kit/memory_cache", __dir__)
24
26
  autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
25
27
  autoload :Adapter, File.expand_path("fixture_kit/adapter", __dir__)
26
28
  autoload :MinitestAdapter, File.expand_path("fixture_kit/adapters/minitest_adapter", __dir__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixture_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
@@ -151,12 +151,21 @@ files:
151
151
  - lib/fixture_kit/adapter.rb
152
152
  - lib/fixture_kit/adapters/minitest_adapter.rb
153
153
  - lib/fixture_kit/adapters/rspec_adapter.rb
154
+ - lib/fixture_kit/analyzer.rb
155
+ - lib/fixture_kit/analyzer/ast_factory_detector.rb
156
+ - lib/fixture_kit/analyzer/file_result.rb
157
+ - lib/fixture_kit/analyzer/group_analyzer.rb
158
+ - lib/fixture_kit/analyzer/let_definition.rb
159
+ - lib/fixture_kit/analyzer/runner.rb
160
+ - lib/fixture_kit/analyzer/text_formatter.rb
154
161
  - lib/fixture_kit/cache.rb
155
162
  - lib/fixture_kit/callbacks.rb
156
163
  - lib/fixture_kit/configuration.rb
157
164
  - lib/fixture_kit/configuration_helper.rb
158
165
  - lib/fixture_kit/definition.rb
166
+ - lib/fixture_kit/file_cache.rb
159
167
  - lib/fixture_kit/fixture.rb
168
+ - lib/fixture_kit/memory_cache.rb
160
169
  - lib/fixture_kit/minitest.rb
161
170
  - lib/fixture_kit/registry.rb
162
171
  - lib/fixture_kit/repository.rb