factory_sloth 1.2.1 β†’ 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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