factory_sloth 1.2.1 β†’ 1.3.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: ce07258dd3b7b7e17ba54accb86be394e97f3f7c986b7d05705dcd54b93e6d50
4
- data.tar.gz: 3160fc1436b8f9271f94b005a367c18084298c003623ca97bb773c9b10656f32
3
+ metadata.gz: 9032b54abec61f25adcfd938a503f312017667f64b4aa1f303ede50a346a62be
4
+ data.tar.gz: 901d358d3572a708ec8190c2d2e59dbdab3bce42f894301284dc285895d48f19
5
5
  SHA512:
6
- metadata.gz: 44dec49c5e35641d9a92bf175bed7afa4a9a58a87e4c663fb50545aaec7551ee49ca88e61d827053c0505d0d0ae9e59d323a7eba3d059f7361d9cc610ba9cf08
7
- data.tar.gz: 0ada9a7c4f0a787bef23d67d4bdcb1dbf80b22294b13467ccdf1b0ed0f044017cac9ffe2e2db4b61622f0da19996b3d4f6d733947fd1f1b3b45e6283ae773abf
6
+ metadata.gz: b1aa29c8c7dd007c5e82ee4cfbc164df1550ace4c2d7880e0471b4a892a2dad33a8e03469c56ec5620f99e7c2dc8c6c47f0d7256102910385acea9a3f97f4b74
7
+ data.tar.gz: bace623884d7e447200af29fb216f8814f86a7bae4deda093105bb9df716c8151332136a473c31cd6c9e51bc07ded9f2306e9eda29b536b7b4aa130384f0059a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.0] - 2023-05-22
4
+
5
+ ### Added
6
+
7
+ - nicer output
8
+ - verbose mode
9
+
10
+ ## [1.2.2] - 2023-05-18
11
+
12
+ ### Fixed
13
+
14
+ - No longer changes create to build for records that are persisted later
15
+ - Fixed duplicate entries in `.factory_sloth_done` file
16
+
3
17
  ## [1.2.1] - 2023-05-17
4
18
 
5
19
  ### Fixed
data/README.md CHANGED
@@ -27,6 +27,7 @@ Examples:
27
27
  Options:
28
28
  -f, --force Ignore ./.factory_sloth_done
29
29
  -l, --lint Dont fix, just list bad create calls
30
+ -V, --verbose Verbose output, useful for debugging
30
31
  -v, --version Show gem version
31
32
  -h, --help Show this help
32
33
  ```
@@ -36,20 +37,25 @@ Options:
36
37
  While running, `factory_sloth` produces output like this:
37
38
 
38
39
  ```
39
- Processing spec/features/sign_up_spec.rb ...
40
- 🟑 2 create calls found, 0 replaced
40
+ 🟑 spec/features/sign_up_spec.rb: 2 create calls found, 0 replaced
41
41
 
42
- Processing spec/lib/string_ext_spec.rb ...
43
- βšͺ️ 0 create calls found, 0 replaced
42
+ βšͺ️ spec/lib/string_ext_spec.rb: 0 create calls found, 0 replaced
44
43
 
45
- Processing spec/models/user_spec.rb ...
46
- - create in line 3 can be replaced with build
47
- - create in line 4 can be replaced with build
48
- 🟒 3 create calls found, 2 replaced
44
+ spec/models/user_spec.rb:3:2: create replaced with build
45
+ expect(create(:user)).not_to be_nil
46
+ ^^^^^^
49
47
 
50
- Processing spec/weird_dir/crazy_spec.rb ...
51
- - create in line 8 can be replaced with build_stubbed
52
- πŸ”΄ 33 create calls found, 0 replaced (conflict)
48
+ spec/models/user_spec.rb:4:2: create_list replaced with build_list
49
+ expect(create_list(:user, 2).count).to eq 2
50
+ ^^^^^^^^^^^
51
+
52
+ 🟒 spec/models/user_spec.rb: 3 create calls found, 2 replaced
53
+
54
+ spec/weird_dir/crazy_spec.rb:8:4: create replaced with build_stubbed
55
+ expect(create(:user)).not_to be_nil
56
+ ^^^^^^
57
+
58
+ πŸ”΄ spec/weird_dir/crazy_spec.rb: 33 create calls found, 0 replaced (conflict)
53
59
 
54
60
  Scanned 4 files, found 2 unnecessary create calls across 1 files and 1 broken specs
55
61
  ```
@@ -58,15 +64,21 @@ The `conflict` case is rare. It only happens if individual examples were green a
58
64
 
59
65
  ## Limitations / known issues
60
66
 
61
- FactorySloth only works with RSpec so far. It also generates false positives in cases where create calls are done but only the *absence* of any effect is tested, e.g.:
67
+ `factory_sloth` only works with RSpec so far. It also works best with unit tests such as model specs. It generates **false positives** in cases where create calls are done but only the *absence* of any effect is tested, e.g.:
68
+
69
+ ```ruby
70
+ user = create(:user)
71
+ User.delete_all
72
+ expect(User.count).to eq 0
73
+ ```
74
+
75
+ This test will still pass if `user` is never inserted into the database in the first place, leading `factory_sloth` to believe that `build` suffices here. However, this change makes the test no longer assert the same thing and reduces coverage. Magic comments can be used to prevent `factory_sloth` from making such changes. `factory_sloth` will not touch lines with inline `# sloth:disable` comments, or sections framed in `# sloth:disable` / `# sloth:enable` comments. Another option is to write the test in a different (and arguably more assertive) way, e.g.:
62
76
 
63
77
  ```ruby
64
- user1 = create(:user, in_search: true)
65
- user2 = create(:user, in_search: false)
66
- expect(User.searchable).to eq(user1)
78
+ expect { User.delete_all }.to change { User.count }.from(1).to(0)
67
79
  ```
68
80
 
69
- This test will still pass if `user2` is no longer inserted into the database, leading factory_sloth to believe that `build` suffices here. However, this makes the test no longer assert the same thing. `# sloth:disable` / `# sloth:enable` comments can be used to prevent `factory_sloth` from making such changes. If you have a good idea about how to detect such cases automatically, let me know :)
81
+ If you have a good idea about how to detect such cases automatically, let me know :)
70
82
 
71
83
  ## Development
72
84
 
@@ -7,8 +7,7 @@ module FactorySloth
7
7
  def call(argv = ARGV)
8
8
  args = option_parser.parse!(argv)
9
9
  specs = SpecPicker.call(paths: args)
10
- forced_files = @force ? specs : args
11
- results = FileProcessor.call(files: specs, forced_files: forced_files, dry_run: @lint)
10
+ results = FileProcessor.call(files: specs, forced_files: args)
12
11
  print_summary(results)
13
12
  end
14
13
 
@@ -29,11 +28,16 @@ module FactorySloth
29
28
  opts.separator 'Options:'
30
29
 
31
30
  opts.on('-f', '--force', "Ignore #{DoneTracker.file}") do
32
- @force = true
31
+ FactorySloth.force = true
33
32
  end
34
33
 
35
34
  opts.on('-l', '--lint', 'Dont fix, just list bad create calls') do
36
- @lint = true
35
+ FactorySloth.dry_run = true
36
+ FactorySloth.lint = true
37
+ end
38
+
39
+ opts.on('-V', '--verbose', 'Verbose output, useful for debugging') do
40
+ FactorySloth.verbose = true
37
41
  end
38
42
 
39
43
  opts.on('-v', '--version', 'Show gem version') do
@@ -49,14 +53,14 @@ module FactorySloth
49
53
  end
50
54
 
51
55
  def print_summary(results)
52
- unnecessary_call_count = results.values.sum { |v| v[:changed_create_calls].count }
53
- changed_specs = results.keys.select { |path| results[path][:changed_create_calls].any? }
56
+ change_sum = results.values.sum { |v| v[:change_count] }
57
+ changed_specs = results.keys.select { |path| results[path][:change_count] > 0 }
54
58
  broken_specs = results.keys.select { |path| !results[path][:ok] }
55
- stats = "Scanned #{results.count} files, found #{unnecessary_call_count}"\
59
+ stats = "Scanned #{results.count} files, found #{change_sum}"\
56
60
  " unnecessary create calls across #{changed_specs.count} files"\
57
61
  "#{" and #{broken_specs.count} broken specs" if broken_specs.any?}"
58
62
 
59
- if @lint && unnecessary_call_count > 0
63
+ if FactorySloth.lint && change_sum > 0
60
64
  warn "#{stats}:\n#{(changed_specs + broken_specs).join("\n")}"
61
65
  exit 1
62
66
  else
@@ -1,87 +1,117 @@
1
- class FactorySloth::CodeMod
2
- attr_reader :create_calls, :changed_create_calls, :path, :original_code, :patched_code
1
+ module FactorySloth
2
+ class CodeMod
3
+ attr_reader :create_calls, :changed_create_calls, :path, :original_code, :patched_code
3
4
 
4
- def self.call(path, code)
5
- new(path, code).tap(&:call)
6
- end
7
-
8
- def initialize(path, code)
9
- self.path = path
10
- self.original_code = code
11
- self.patched_code = code
12
- end
5
+ require 'forwardable'
6
+ extend Forwardable
13
7
 
14
- def call
15
- self.create_calls = FactorySloth::CreateCallFinder.call(code: original_code)
16
-
17
- # Performance note: it might be faster to write ALL possible patches for a
18
- # given spec file to tempfiles first, and then run them all in a single
19
- # rspec call. However, this would make it impossible to use `--fail-fast`,
20
- # and might make examples fail that are not as idempotent as they should be.
21
- self.changed_create_calls =
22
- create_calls
23
- .sort_by { |call| [-call.line, -call.column] }
24
- .select { |call| try_patch(call, 'build') || try_patch(call, 'build_stubbed') }
25
-
26
- # validate whole spec after changes, e.g. to detect side-effects
27
- self.ok = changed_create_calls.none? ||
28
- FactorySloth::SpecRunner.call(path, patched_code)
29
- changed_create_calls.clear unless ok?
30
- patched_code.replace(original_code) unless ok?
31
- end
8
+ def_delegator :changed_create_calls, :any?, :changed?
9
+ def_delegator :changed_create_calls, :count, :change_count
10
+ def_delegator :create_calls, :count, :create_count
32
11
 
33
- def ok?
34
- @ok
35
- end
12
+ def self.call(path, code)
13
+ new(path, code).tap(&:call)
14
+ end
36
15
 
37
- def changed?
38
- change_count > 0
39
- end
16
+ def initialize(path, code)
17
+ self.path = path
18
+ self.original_code = code
19
+ self.patched_code = code
20
+ end
40
21
 
41
- def create_count
42
- create_calls.count
43
- end
22
+ def call
23
+ self.create_calls = CreateCallFinder.call(code: original_code)
44
24
 
45
- def change_count
46
- changed_create_calls.count
47
- end
25
+ # Performance note: it might be faster to write ALL possible patches for a
26
+ # given spec file to tempfiles first, and then run them all in a single
27
+ # rspec call. However, this would make it impossible to use `--fail-fast`,
28
+ # and might make examples fail that are not as idempotent as they should be.
29
+ self.changed_create_calls =
30
+ create_calls.sort_by { |call| [-call.line, -call.column] }.select do |call|
31
+ build_result = try_patch(call, 'build')
32
+ next if build_result == ABORT
48
33
 
49
- private
34
+ build_result == SUCCESS || try_patch(call, 'build_stubbed') == SUCCESS
35
+ end
50
36
 
51
- attr_writer :create_calls, :changed_create_calls, :ok, :path, :original_code, :patched_code
37
+ # validate whole spec after changes, e.g. to detect side-effects
38
+ self.ok = changed_create_calls.none? || begin
39
+ FactorySloth.verbose && puts("Checking whole file after changes")
40
+ run(patched_code).success?
41
+ end
42
+ ok? || changed_create_calls.clear && patched_code.replace(original_code)
43
+ end
52
44
 
53
- def try_patch(call, base_variant)
54
- variant = call.name.sub('create', base_variant)
55
- new_patched_code = patched_code.sub(
56
- /\A(?:.*\n){#{call.line - 1}}.{#{call.column}}\K#{call.name}/,
57
- variant
58
- )
59
- checked_patched_code = with_execution_check(new_patched_code, call.line, variant)
60
- if FactorySloth::SpecRunner.call(path, checked_patched_code, line: call.line)
61
- puts "- #{call.name} in line #{call.line} can be replaced with #{variant}"
62
- self.patched_code = new_patched_code
45
+ def ok?
46
+ @ok
63
47
  end
64
- end
65
48
 
66
- def with_execution_check(spec_code, line, variant)
67
- spec_code + <<~RUBY
68
- ; defined?(FactoryBot) && defined?(RSpec) && RSpec.configure do |config|
69
- executed_lines = []
49
+ def message
50
+ stats = "#{path}: #{create_count} create calls found, #{change_count} "\
51
+ "#{FactorySloth.dry_run ? 'replaceable' : 'replaced'}"
70
52
 
71
- FactoryBot::Syntax::Methods.class_eval do
72
- alias ___original_#{variant} #{variant}
53
+ return "πŸ”΄ #{stats} (conflict)" unless ok?
73
54
 
74
- define_method("#{variant}") do |*args, **kwargs, &blk|
75
- executed_lines << caller_locations(1, 1)&.first&.lineno
76
- ___original_#{variant}(*args, **kwargs, &blk)
77
- end
78
- end
55
+ if create_count == 0
56
+ "βšͺ️ #{stats}"
57
+ elsif change_count == 0
58
+ "🟑 #{stats}"
59
+ else
60
+ "🟒 #{stats}"
61
+ end
62
+ end
79
63
 
80
- config.after(:suite) do
81
- executed_lines.include?(#{line}) ||
82
- fail("unused factory in line #{line} - will not be modified")
83
- end
64
+ private
65
+
66
+ attr_writer :create_calls, :changed_create_calls, :ok, :path, :original_code, :patched_code
67
+
68
+ def try_patch(call, base_variant)
69
+ variant = call.name.sub('create', base_variant)
70
+ FactorySloth.verbose && puts("#{link_to_call(call)}: trying #{variant} ...")
71
+
72
+ new_patched_code = patched_code.sub(
73
+ /\A(?:.*\R){#{call.line - 1}}.{#{call.column}}\K#{call.name}/,
74
+ variant
75
+ )
76
+ checked_patched_code = new_patched_code + ExecutionCheck.for(call.line, variant)
77
+
78
+ result = run(checked_patched_code, line: call.line)
79
+
80
+ if result.success?
81
+ info = FactorySloth.dry_run ? 'can be replaced' : 'replaced'
82
+ puts call_message(call, "#{info} with #{variant}"), ''
83
+ self.patched_code = new_patched_code
84
+ SUCCESS
85
+ elsif result.exitstatus == ExecutionCheck::FACTORY_UNUSED_CODE
86
+ puts call_message(call, "is never executed, skipping"), ''
87
+ ABORT
88
+ elsif result.exitstatus == ExecutionCheck::FACTORY_PERSISTED_LATER_CODE
89
+ FactorySloth.verbose && puts("Record is persisted later, skipping")
90
+ ABORT
84
91
  end
85
- RUBY
92
+ end
93
+
94
+ def run(code, line: nil)
95
+ result = SpecRunner.call(path, code, line: line)
96
+ FactorySloth.verbose && puts(' RSpec output:', result.output.gsub(/^/, ' '))
97
+ result
98
+ end
99
+
100
+ ABORT = :ABORT # returned if there is no need to try other variants
101
+ SUCCESS = :SUCCESS
102
+
103
+ def call_message(call, message)
104
+ line_content = original_code[/\A(?:.*\R){#{call.line - 1}}\K.*/]
105
+ indent = line_content[/^\s*/]
106
+
107
+ "#{link_to_call(call)}: #{call.name} #{message}\n"\
108
+ " #{line_content.delete_prefix(indent)}\n"\
109
+ " #{' ' * (call.column - indent.size)}#{Color.yellow('^' * call.name.size)}"
110
+ end
111
+
112
+ def link_to_call(call)
113
+ # note: column from Ripper is 0-indexed, editors expect 1-indexed columns
114
+ Color.light_blue("#{path}:#{call.line}:#{call.column + 1}")
115
+ end
86
116
  end
87
117
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FactorySloth::Color
4
+ extend self
5
+
6
+ def yellow(str)
7
+ colorize(str, 33)
8
+ end
9
+
10
+ def light_blue(str)
11
+ colorize(str, 36)
12
+ end
13
+
14
+ private
15
+
16
+ def colorize(str, color_code)
17
+ return str unless $stdout.is_a?(IO) && $stdout.tty?
18
+
19
+ "\e[#{color_code}m#{str}\e[0m"
20
+ end
21
+ end
@@ -1 +1 @@
1
- FactorySloth::CreateCall = Struct.new(:name, :line, :column, keyword_init: true)
1
+ FactorySloth::CreateCall = Struct.new(:column, :line, :name, keyword_init: true)
@@ -7,6 +7,8 @@ module FactorySloth::DoneTracker
7
7
 
8
8
  def mark_as_done(path)
9
9
  normalized_path = normalize(path)
10
+ return if done?(normalized_path)
11
+
10
12
  done << normalized_path
11
13
  File.open(file, 'a') { |f| f.puts(normalized_path) }
12
14
  end
@@ -0,0 +1,39 @@
1
+ # This adds code that makes a spec run fail and thus prevents changes if:
2
+ # a) the patched factory in the given line is never called
3
+ # b) the built record was persisted later anyway
4
+ # The rationale behind a) is that things like skipped examples should not
5
+ # be broken. The rationale behind b) is that not much DB work would be saved,
6
+ # but diff noise would be increased and ease of editing the example reduced.
7
+
8
+ module FactorySloth::ExecutionCheck
9
+ FACTORY_UNUSED_CODE = 77
10
+ FACTORY_PERSISTED_LATER_CODE = 78
11
+
12
+ def self.for(line, variant)
13
+ <<~RUBY
14
+ ; defined?(FactoryBot) && defined?(RSpec) && RSpec.configure do |config|
15
+ records_by_line = {} # track records initialized through factories per line
16
+
17
+ FactoryBot::Syntax::Methods.class_eval do
18
+ alias ___original_#{variant} #{variant} # e.g. ___original_build build
19
+
20
+ define_method("#{variant}") do |*args, **kwargs, &blk| # e.g. build
21
+ result = ___original_#{variant}(*args, **kwargs, &blk)
22
+ list = records_by_line[caller_locations(1, 1)&.first&.lineno] ||= []
23
+ list.concat([result].flatten) # to work with single, list, and pair
24
+ result
25
+ end
26
+ end
27
+
28
+ config.after(:suite) do
29
+ records = records_by_line[#{line}]
30
+ records&.any? || exit!(#{FACTORY_UNUSED_CODE})
31
+ unless "#{variant}".include?('stub') # factory_bot stub stubs persisted? as true
32
+ records.any? { |r| r.respond_to?(:persisted?) && r.persisted? } &&
33
+ exit!(#{FACTORY_PERSISTED_LATER_CODE})
34
+ end
35
+ end
36
+ end
37
+ RUBY
38
+ end
39
+ end
@@ -2,46 +2,30 @@ module FactorySloth
2
2
  module FileProcessor
3
3
  extend self
4
4
 
5
- def call(files:, forced_files: [], dry_run: false)
5
+ def call(files:, forced_files: [])
6
6
  files.each_with_object({}) do |path, acc|
7
- puts "Processing #{path} ..."
8
-
9
- if DoneTracker.done?(path) && !forced_files.include?(path)
10
- puts "πŸ”΅ Skipped (marked as done in #{DoneTracker.file})", ''
7
+ if DoneTracker.done?(path) &&
8
+ !(FactorySloth.force || forced_files.include?(path))
9
+ puts "πŸ”΅ #{path}: skipped (marked as done in #{DoneTracker.file})", ''
11
10
  next
12
11
  end
13
12
 
14
- result = process(path, dry_run: dry_run)
15
- acc[path] = { ok: result.ok?, changed_create_calls: result.changed_create_calls }
13
+ result = process(path)
14
+ acc[path] = { ok: result.ok?, change_count: result.change_count }
16
15
  DoneTracker.mark_as_done(path)
17
16
  end
18
17
  end
19
18
 
20
19
  private
21
20
 
22
- def process(path, dry_run:)
21
+ def process(path)
23
22
  code = File.read(path)
24
23
  result = CodeMod.call(path, code)
25
- unless dry_run
24
+ unless FactorySloth.dry_run
26
25
  File.write(path, result.patched_code) if result.changed?
27
26
  end
28
- puts result_message(result, dry_run), ''
27
+ puts result.message, ''
29
28
  result
30
29
  end
31
-
32
- def result_message(result, dry_run)
33
- stats = "#{result.create_count} create calls found, "\
34
- "#{result.change_count} #{dry_run ? 'replaceable' : 'replaced'}"
35
-
36
- return "πŸ”΄ #{stats} (conflict)" unless result.ok?
37
-
38
- if result.create_count == 0
39
- "βšͺ️ #{stats}"
40
- elsif result.change_count == 0
41
- "🟑 #{stats}"
42
- else
43
- "🟒 #{stats}"
44
- end
45
- end
46
30
  end
47
31
  end
@@ -1,13 +1,28 @@
1
+ require 'open3'
1
2
  require 'tmpdir'
2
3
 
3
4
  module FactorySloth::SpecRunner
4
5
  def self.call(spec_path, spec_code, line: nil)
5
- Dir.mktmpdir do |tmpdir|
6
- path = File.join(tmpdir, spec_path)
7
- FileUtils.mkdir_p(File.dirname(path))
8
- File.write(path, spec_code)
9
- path_arg = [path, line].compact.map(&:to_s).join(':')
10
- !!system("bundle exec rspec #{path_arg} --fail-fast 1>/dev/null 2>&1")
6
+ path = File.join(tmpdir, spec_path)
7
+ FileUtils.mkdir_p(File.dirname(path))
8
+ File.write(path, spec_code)
9
+ path_arg = [path, line].compact.map(&:to_s).join(':')
10
+ command = "bundle exec rspec #{path_arg} --fail-fast --order defined 2>&1"
11
+ output, process_status = Open3.capture2(command)
12
+ Result.new(output: output, process_status: process_status)
13
+ end
14
+
15
+ Result = Struct.new(:output, :process_status, keyword_init: true) do
16
+ require 'forwardable'
17
+ extend Forwardable
18
+ def_delegators :process_status, :exitstatus, :success?
19
+ end
20
+
21
+ def self.tmpdir
22
+ @tmpdir ||= begin
23
+ dir = Dir.mktmpdir('factory_sloth-')
24
+ at_exit { FileUtils.remove_entry(dir) if File.exist?(dir) }
25
+ dir
11
26
  end
12
27
  end
13
28
  end
@@ -1,3 +1,3 @@
1
1
  module FactorySloth
2
- VERSION = '1.2.1'
2
+ VERSION = '1.3.0'
3
3
  end
data/lib/factory_sloth.rb CHANGED
@@ -1,10 +1,14 @@
1
- module FactorySloth; end
1
+ module FactorySloth
2
+ singleton_class.attr_accessor :dry_run, :force, :lint, :verbose
3
+ end
2
4
 
3
5
  require_relative 'factory_sloth/cli'
4
6
  require_relative 'factory_sloth/code_mod'
7
+ require_relative 'factory_sloth/color'
5
8
  require_relative 'factory_sloth/create_call'
6
9
  require_relative 'factory_sloth/create_call_finder'
7
10
  require_relative 'factory_sloth/done_tracker'
11
+ require_relative 'factory_sloth/execution_check'
8
12
  require_relative 'factory_sloth/file_processor'
9
13
  require_relative 'factory_sloth/spec_picker'
10
14
  require_relative 'factory_sloth/spec_runner'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_sloth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janosch Müller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-17 00:00:00.000000000 Z
11
+ date: 2023-05-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -28,9 +28,11 @@ files:
28
28
  - lib/factory_sloth.rb
29
29
  - lib/factory_sloth/cli.rb
30
30
  - lib/factory_sloth/code_mod.rb
31
+ - lib/factory_sloth/color.rb
31
32
  - lib/factory_sloth/create_call.rb
32
33
  - lib/factory_sloth/create_call_finder.rb
33
34
  - lib/factory_sloth/done_tracker.rb
35
+ - lib/factory_sloth/execution_check.rb
34
36
  - lib/factory_sloth/file_processor.rb
35
37
  - lib/factory_sloth/spec_picker.rb
36
38
  - lib/factory_sloth/spec_runner.rb