factory_trace 2.0.0 → 3.0.1

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: 8eb259ef9d3572a820e5fddc7a65d484f29a8d25bfdfa5947a8306cf3c22435f
4
- data.tar.gz: 0b9eeaea9bf6355772994e4ca4d1dd7b62b82d43a00000c4453be067cad21376
3
+ metadata.gz: 9150973821d1c8196bf9dc7ba0ccbaa9c5e81cc397fed0613f2b0e0086104ec7
4
+ data.tar.gz: cbbf862a0a4aa1d3a436329dd4e2d2f94c2c700a1f5e0a07da5b08395f4fa89e
5
5
  SHA512:
6
- metadata.gz: 21c794f7215824581bd33ad9f90235c7c929e71803ace8bb3dfd8d0fe27ab912c052633141c21d23180b5244228b4ba38ef180284c1327da41678e618887d6f5
7
- data.tar.gz: 3ef9526dfba81da0a0316aa4c45caa3b7a404c87e2573a320e719d03cc5f72d0bd041356d00d81a4dadb6fdaf8cacceb0c384a3c053ad096d6bfe3004f512807
6
+ metadata.gz: c12b5e6ee4de364e37c99a01c363931c35f6d5f32af1a9525e9ed990c11209c38473f1a9809eb2bc980237cb069cbffb017f3c44c50bb33f5b41b90645f46037
7
+ data.tar.gz: 245cc1ad516ff4a706f6f32fa6c96f2bf8d55b4ec032e9ebe10ca835f02bd06b501f94a5b5b9c742c2962d56c6c3e3eddfe55fa42e3e6d95376b545e2693ed6f
data/exe/factory_trace CHANGED
@@ -7,9 +7,16 @@ require "factory_trace"
7
7
  fail "You should pass at least one file with traced information.\nYou can generate it using only_trace mode." if ARGV.empty?
8
8
 
9
9
  config = FactoryTrace.configuration
10
- hash = FactoryTrace::Readers::TraceReader.read_from_files(*ARGV)
11
- reports = FactoryTrace::Processors::FindUnused.call(hash[:defined], hash[:used])
12
- code = (reports.any? { |report| report[:code] == :unused && !report.key?(:value) }) ? 1 : 0
10
+ writer = FactoryTrace::Writers::ReportWriter.new(config.out, config)
11
+ code = 0
12
+
13
+ %i[factory_bot fixtures].each do |kind|
14
+ result = FactoryTrace::Readers::TraceReader.read_from_files(kind, *ARGV)
15
+ reports = FactoryTrace::Processors::FindUnused.call(result[:defined], result[:used])
16
+ if reports.any? { |report| report[:code] == :unused && !report.key?(:value) }
17
+ code = 1
18
+ end
19
+ writer.write(reports, kind: kind)
20
+ end
13
21
 
14
- FactoryTrace::Writers::ReportWriter.new(config.out, config).write(reports)
15
22
  exit(code)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module FactoryTrace
4
4
  class Configuration
5
- attr_accessor :path, :enabled, :color, :mode, :trace_definition
5
+ attr_accessor :path, :enabled, :color, :mode, :trace_definition, :fixture_path
6
6
 
7
7
  def initialize
8
8
  @enabled = ENV.key?("FB_TRACE") || ENV.key?("FB_TRACE_FILE")
@@ -10,6 +10,7 @@ module FactoryTrace
10
10
  @color = path.nil?
11
11
  @mode = extract_mode(ENV["FB_TRACE"]) || :full
12
12
  @trace_definition = true
13
+ @fixture_path = "test/fixtures"
13
14
  end
14
15
 
15
16
  def trace_definition?
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FactoryTrace
4
+ class FixtureTracker
5
+ attr_reader :storage
6
+
7
+ def initialize
8
+ @storage = {}
9
+ end
10
+
11
+ def track!
12
+ tracker_storage = @storage
13
+
14
+ ::ActiveRecord::FixtureSet.prepend(Module.new do
15
+ define_method(:[]) do |fixture_name|
16
+ if fixture_name
17
+ tracker_storage[name] ||= Set.new
18
+ tracker_storage[name].add(fixture_name.to_s)
19
+ end
20
+ super(fixture_name)
21
+ end
22
+ end)
23
+ end
24
+ end
25
+ end
@@ -10,7 +10,7 @@ module FactoryTrace
10
10
  proxy = FactoryBot::DefinitionProxy.new(factory.definition)
11
11
  proxy.instance_eval(&block) if block
12
12
 
13
- REGISTER.register_factory(factory)
13
+ FactoryTrace.factory_bot_register.register_factory(factory)
14
14
 
15
15
  proxy.child_factories.each do |(child_name, child_caller_location, child_options, child_block)|
16
16
  parent_factory = child_options.delete(:parent) || name
@@ -19,7 +19,7 @@ module FactoryTrace
19
19
  end
20
20
 
21
21
  def trait(name, &block)
22
- REGISTER.register_trait(FactoryBot::Trait.new(name, Helpers::Caller.location, &block))
22
+ FactoryTrace.factory_bot_register.register_trait(FactoryBot::Trait.new(name, Helpers::Caller.location, &block))
23
23
  end
24
24
  end
25
25
  end
@@ -2,6 +2,5 @@
2
2
 
3
3
  module FactoryTrace
4
4
  module MonkeyPatches
5
- REGISTER = (FactoryBot::VERSION >= "5.1.0") ? FactoryBot::Internal : FactoryBot
6
5
  end
7
6
  end
@@ -7,7 +7,7 @@ module FactoryTrace
7
7
  def self.call
8
8
  collection = FactoryTrace::Structures::Collection.new
9
9
 
10
- MonkeyPatches::REGISTER.traits.each do |trait|
10
+ FactoryTrace.factory_bot_register.traits.each do |trait|
11
11
  collection.add(FactoryTrace::Helpers::Converter.trait(trait))
12
12
  end
13
13
 
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FactoryTrace
4
+ module Preprocessors
5
+ class ExtractDefinedFixtures
6
+ # Extracts all defined fixture sets and their entries from fixture YAML files
7
+ #
8
+ # @param [String, Array<String>] fixture_path path(s) to directories containing fixture YAML files
9
+ #
10
+ # @return [FactoryTrace::Structures::Collection]
11
+ def self.call(fixture_path)
12
+ collection = FactoryTrace::Structures::Collection.new
13
+
14
+ Array(fixture_path).each do |path|
15
+ Dir.glob(File.join(path, "**", "*.yml")).sort.each do |file|
16
+ fixture_set_name = File.basename(file, ".yml")
17
+ entries = parse_fixture_file(file)
18
+
19
+ traits = entries.map do |entry_name, definition_path|
20
+ FactoryTrace::Structures::Trait.new(entry_name, definition_path: definition_path)
21
+ end
22
+
23
+ factory = FactoryTrace::Structures::Factory.new(
24
+ [fixture_set_name],
25
+ traits,
26
+ definition_path: "#{file}:1"
27
+ )
28
+
29
+ collection.add(factory)
30
+ end
31
+ end
32
+
33
+ collection
34
+ end
35
+
36
+ # Parses a fixture YAML file and returns a hash of entry names to definition paths
37
+ #
38
+ # @param [String] file_path
39
+ #
40
+ # @return [Hash<String, String>]
41
+ def self.parse_fixture_file(file_path)
42
+ entries = {}
43
+ lineno = 1
44
+
45
+ File.foreach(file_path) do |line|
46
+ if (match = line.match(/\A(\w+):(?:\s|$)/))
47
+ entries[match[1]] = "#{file_path}:#{lineno}"
48
+ end
49
+ lineno += 1
50
+ end
51
+
52
+ entries
53
+ end
54
+
55
+ private_class_method :parse_fixture_file
56
+ end
57
+ end
58
+ end
@@ -3,20 +3,20 @@
3
3
  module FactoryTrace
4
4
  module Readers
5
5
  class TraceReader
6
- attr_reader :io, :configuration
6
+ attr_reader :io, :configuration, :kind
7
7
 
8
8
  # Read the data from files and merge it
9
9
  #
10
10
  # @return [Hash<Symbol, FactoryTrace::Structures::Collection>]
11
- def self.read_from_files(*file_names, configuration: Configuration.new)
11
+ def self.read_from_files(kind, *file_names, configuration: Configuration.new)
12
12
  result = {defined: FactoryTrace::Structures::Collection.new, used: FactoryTrace::Structures::Collection.new}
13
13
 
14
14
  file_names.each do |file_name|
15
15
  File.open(file_name, "r") do |file|
16
- data = new(file, configuration: configuration).read
16
+ data = new(kind, file, configuration: configuration).read
17
17
 
18
18
  [:defined, :used].each do |key|
19
- result[key].merge!(data[key])
19
+ result[key].merge!(data[key]) unless data.empty?
20
20
  end
21
21
  end
22
22
  end
@@ -24,7 +24,8 @@ module FactoryTrace
24
24
  result
25
25
  end
26
26
 
27
- def initialize(io, configuration: Configuration.new)
27
+ def initialize(kind, io, configuration: Configuration.new)
28
+ @kind = kind
28
29
  @io = io
29
30
  @configuration = configuration
30
31
  end
@@ -35,9 +36,14 @@ module FactoryTrace
35
36
  def read
36
37
  hash = JSON.parse(io.read)
37
38
 
39
+ defined = hash["defined"][kind.to_s]
40
+ used = hash["used"][kind.to_s]
41
+
42
+ return {} if defined.nil? || used.nil?
43
+
38
44
  {
39
- defined: parse_collection(hash["defined"]),
40
- used: parse_collection(hash["used"])
45
+ defined: parse_collection(defined),
46
+ used: parse_collection(used)
41
47
  }
42
48
  end
43
49
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FactoryTrace
4
- VERSION = "2.0.0"
4
+ VERSION = "3.0.1"
5
5
  end
@@ -16,11 +16,14 @@ module FactoryTrace
16
16
  }.freeze
17
17
 
18
18
  # @param [Array<Hash>] results
19
- def write(results)
19
+ # @param [Symbol] kind - :factory (default) or :fixture
20
+ def write(results, kind:)
21
+ io.puts("") unless file?
22
+
20
23
  total_color = (results.any? { |result| result[:code] == :unused && !result.key?(:value) }) ? :red : :green
21
24
 
22
25
  results.each do |result|
23
- io.puts(convert(result, total_color: total_color))
26
+ io.puts(convert(result, total_color: total_color, kind: kind))
24
27
  end
25
28
  end
26
29
 
@@ -28,13 +31,23 @@ module FactoryTrace
28
31
 
29
32
  # @param [Hash<Symbol, Object>] result
30
33
  # @param [Symbol] total_color
31
- def convert(result, total_color:)
34
+ # @param [Symbol] kind - :factory_bot or :fixtures
35
+ def convert(result, total_color:, kind:)
32
36
  if result[:value]
33
- colorize(total_color, "total number of unique #{humanize_code(result[:code])} factories & traits: #{result[:value]}")
37
+ label = (kind == :fixtures) ? "fixture sets & entries" : "factories & traits"
38
+ colorize(total_color, "total number of unique #{humanize_code(result[:code])} #{label}: #{result[:value]}")
34
39
  elsif result[:factory_names] && result[:trait_name]
35
- append_definition_path(result) { "#{humanize_code(result[:code])} trait #{colorize(:blue, result[:trait_name])} of factory #{list(result[:factory_names])}" }
40
+ if kind == :fixtures
41
+ append_definition_path(result) { "#{humanize_code(result[:code])} fixture entry #{colorize(:blue, result[:trait_name])} of fixture set #{list(result[:factory_names])}" }
42
+ else
43
+ append_definition_path(result) { "#{humanize_code(result[:code])} trait #{colorize(:blue, result[:trait_name])} of factory #{list(result[:factory_names])}" }
44
+ end
36
45
  elsif result[:factory_names]
37
- append_definition_path(result) { "#{humanize_code(result[:code])} factory #{list(result[:factory_names])}" }
46
+ if kind == :fixtures
47
+ append_definition_path(result) { "#{humanize_code(result[:code])} fixture set #{list(result[:factory_names])}" }
48
+ else
49
+ append_definition_path(result) { "#{humanize_code(result[:code])} factory #{list(result[:factory_names])}" }
50
+ end
38
51
  else
39
52
  append_definition_path(result) { "#{humanize_code(result[:code])} global trait #{colorize(:blue, result[:trait_name])}" }
40
53
  end
@@ -3,8 +3,8 @@
3
3
  module FactoryTrace
4
4
  module Writers
5
5
  class TraceWriter < Writer
6
- # @param [FactoryTrace::Structures::Collection] defined
7
- # @param [FactoryTrace::Structures::Collection] used
6
+ # @param [Hash] defined
7
+ # @param [Hash] used
8
8
  def write(defined, used)
9
9
  io.puts(JSON.pretty_generate(defined: defined.to_h, used: used.to_h))
10
10
  end
@@ -9,6 +9,10 @@ module FactoryTrace
9
9
  @io = io
10
10
  @configuration = configuration
11
11
  end
12
+
13
+ def file?
14
+ io.is_a?(File)
15
+ end
12
16
  end
13
17
  end
14
18
  end
data/lib/factory_trace.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  # External dependencies
4
4
  require "active_support"
5
- require "factory_bot"
6
5
  require "json"
7
6
  require "set"
8
7
  require "pathname"
@@ -13,6 +12,7 @@ require "factory_trace/helpers/converter"
13
12
  require "factory_trace/helpers/statusable"
14
13
  require "factory_trace/helpers/caller"
15
14
  require "factory_trace/tracker"
15
+ require "factory_trace/fixture_tracker"
16
16
 
17
17
  require "factory_trace/structures/factory"
18
18
  require "factory_trace/structures/trait"
@@ -20,6 +20,7 @@ require "factory_trace/structures/collection"
20
20
 
21
21
  require "factory_trace/preprocessors/extract_defined"
22
22
  require "factory_trace/preprocessors/extract_used"
23
+ require "factory_trace/preprocessors/extract_defined_fixtures"
23
24
 
24
25
  require "factory_trace/processors/find_unused"
25
26
 
@@ -44,19 +45,30 @@ module FactoryTrace
44
45
  return unless configuration.enabled
45
46
  trace_definitions! if configuration.trace_definition?
46
47
 
47
- tracker.track!
48
+ tracker.track! if factory_bot?
49
+ fixture_tracker.track! if fixtures?
48
50
  end
49
51
 
50
52
  def stop
51
53
  return unless configuration.enabled
52
54
 
53
55
  # This is required to exclude parent traits from +defined_traits+
54
- FactoryBot.reload
56
+ FactoryBot.reload if factory_bot?
55
57
 
56
58
  if configuration.mode?(:full)
57
- Writers::ReportWriter.new(configuration.out, configuration).write(Processors::FindUnused.call(defined, used))
59
+ writer = Writers::ReportWriter.new(configuration.out, configuration)
60
+ writer.write(Processors::FindUnused.call(defined, used), kind: :factory_bot) if factory_bot?
61
+ writer.write(Processors::FindUnused.call(defined_fixtures, used_fixtures), kind: :fixtures) if fixtures?
58
62
  elsif configuration.mode?(:trace_only)
59
- Writers::TraceWriter.new(configuration.out, configuration).write(defined, used)
63
+ writer = Writers::TraceWriter.new(configuration.out, configuration)
64
+ writer.write(mix(defined_fixtures, defined), mix(used_fixtures, used))
65
+ end
66
+ end
67
+
68
+ def mix(fixtures, factory_bot)
69
+ {}.tap do |result|
70
+ result[:fixtures] = fixtures.to_h if fixtures?
71
+ result[:factory_bot] = factory_bot.to_h if factory_bot?
60
72
  end
61
73
  end
62
74
 
@@ -68,6 +80,10 @@ module FactoryTrace
68
80
  @configuration ||= Configuration.new
69
81
  end
70
82
 
83
+ def factory_bot_register
84
+ @factory_bot_register ||= (FactoryBot::VERSION >= "5.1.0") ? FactoryBot::Internal : FactoryBot
85
+ end
86
+
71
87
  private
72
88
 
73
89
  def used
@@ -82,7 +98,29 @@ module FactoryTrace
82
98
  @tracker ||= Tracker.new
83
99
  end
84
100
 
101
+ def fixture_tracker
102
+ @fixture_tracker ||= FixtureTracker.new
103
+ end
104
+
105
+ def defined_fixtures
106
+ @defined_fixtures ||= Preprocessors::ExtractDefinedFixtures.call(configuration.fixture_path)
107
+ end
108
+
109
+ def used_fixtures
110
+ @used_fixtures ||= Preprocessors::ExtractUsed.call(fixture_tracker.storage)
111
+ end
112
+
113
+ def factory_bot?
114
+ defined?(FactoryBot)
115
+ end
116
+
117
+ def fixtures?
118
+ defined?(ActiveRecord::FixtureSet)
119
+ end
120
+
85
121
  def trace_definitions!
122
+ return unless factory_bot?
123
+
86
124
  FactoryBot::Factory.prepend(FactoryTrace::MonkeyPatches::Factory)
87
125
  FactoryBot::Trait.prepend(FactoryTrace::MonkeyPatches::Trait)
88
126
  FactoryBot::Syntax::Default::DSL.prepend(FactoryTrace::MonkeyPatches::Default::DSL)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_trace
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - djezzzl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-10 00:00:00.000000000 Z
11
+ date: 2026-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: factory_bot
@@ -17,7 +17,21 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
- type: :runtime
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
@@ -91,6 +105,7 @@ files:
91
105
  - exe/factory_trace
92
106
  - lib/factory_trace.rb
93
107
  - lib/factory_trace/configuration.rb
108
+ - lib/factory_trace/fixture_tracker.rb
94
109
  - lib/factory_trace/helpers/caller.rb
95
110
  - lib/factory_trace/helpers/converter.rb
96
111
  - lib/factory_trace/helpers/statusable.rb
@@ -100,6 +115,7 @@ files:
100
115
  - lib/factory_trace/monkey_patches/monkey_patches.rb
101
116
  - lib/factory_trace/monkey_patches/trait.rb
102
117
  - lib/factory_trace/preprocessors/extract_defined.rb
118
+ - lib/factory_trace/preprocessors/extract_defined_fixtures.rb
103
119
  - lib/factory_trace/preprocessors/extract_used.rb
104
120
  - lib/factory_trace/processors/find_unused.rb
105
121
  - lib/factory_trace/readers/trace_reader.rb