factory_sloth 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +10 -0
- data/lib/factory_sloth/cli.rb +18 -6
- data/lib/factory_sloth/code_mod.rb +22 -25
- data/lib/factory_sloth/create_call_finder.rb +20 -5
- data/lib/factory_sloth/file_processor.rb +4 -4
- data/lib/factory_sloth/spec_runner.rb +13 -0
- data/lib/factory_sloth/version.rb +1 -1
- data/lib/factory_sloth.rb +1 -0
- metadata +3 -3
- data/Gemfile.lock +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3550aaf57e03aa0da8d68264bab3c4a0a17ed264995859f34cc641e7e376b51b
|
4
|
+
data.tar.gz: d947f5e3533b24b7740d509b5e9b8f3a832a6c535eb81f859ba7c562127c3e5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9abcbc30c854da1ec58f4ab292ab6ed9a851dcbd11bd00291b2902bdec14ec79c21145033c2a984b7243831dd27b426bf5e33f6e9d22272d673a9ff2f61dd97
|
7
|
+
data.tar.gz: '03280bc6477e646583c5af49d46e8bacbf611387181c3e0625e007c019170b697151a78fbd1698a859358a512f483a9949f3b6b27006fe92fc85fd10ccb2b149'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.2.0] - 2023-05-16
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Added support for magic comments `# sloth:disable`, `# sloth:enable`
|
8
|
+
|
9
|
+
## [1.1.0] - 2023-05-16
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Added summary at end of CLI output
|
14
|
+
|
3
15
|
## [1.0.1] - 2023-05-14
|
4
16
|
|
5
17
|
### Fixed
|
data/README.md
CHANGED
@@ -50,10 +50,20 @@ Processing spec/models/user_spec.rb ...
|
|
50
50
|
Processing spec/weird_dir/crazy_spec.rb ...
|
51
51
|
- create in line 8 can be replaced with build_stubbed
|
52
52
|
🔴 33 create calls found, 0 replaced (conflict)
|
53
|
+
|
54
|
+
Scanned 4 files, found 2 unnecessary create calls across 1 files and 1 broken specs
|
53
55
|
```
|
54
56
|
|
55
57
|
The `conflict` case is rare. It only happens if individual examples were green after changing them, but at least one example failed when evaluating the whole file after all changes. This probably means that some other example was red even before making changes, or that something else is wrong with this spec file, e.g. some examples depend on other examples' side effects.
|
56
58
|
|
59
|
+
## Limitations
|
60
|
+
|
61
|
+
- only works with RSpec so far
|
62
|
+
- downgrades create calls that never run (e.g. in skipped examples)
|
63
|
+
- downgrades create calls that are only checked for an absence of effects
|
64
|
+
- e.g. `a = create(:a); b = create(:b); expect(Record.filtered).to eq [b]`
|
65
|
+
- `# sloth:disable` / `# sloth:enable` comments can be used to control this
|
66
|
+
|
57
67
|
## Development
|
58
68
|
|
59
69
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/factory_sloth/cli.rb
CHANGED
@@ -8,12 +8,8 @@ module FactorySloth
|
|
8
8
|
args = option_parser.parse!(argv)
|
9
9
|
specs = SpecPicker.call(paths: args)
|
10
10
|
forced_files = @force ? specs : args
|
11
|
-
|
12
|
-
|
13
|
-
if @lint && bad_specs.any?
|
14
|
-
warn "Found unnecessary create calls in:\n#{bad_specs.join("\n")}"
|
15
|
-
exit 1
|
16
|
-
end
|
11
|
+
results = FileProcessor.call(files: specs, forced_files: forced_files, dry_run: @lint)
|
12
|
+
print_summary(results)
|
17
13
|
end
|
18
14
|
|
19
15
|
private
|
@@ -51,5 +47,21 @@ module FactorySloth
|
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
50
|
+
|
51
|
+
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? }
|
54
|
+
broken_specs = results.keys.select { |path| !results[path][:ok] }
|
55
|
+
stats = "Scanned #{results.count} files, found #{unnecessary_call_count}"\
|
56
|
+
" unnecessary create calls across #{changed_specs.count} files"\
|
57
|
+
"#{" and #{broken_specs.count} broken specs" if broken_specs.any?}"
|
58
|
+
|
59
|
+
if @lint && unnecessary_call_count > 0
|
60
|
+
warn "#{stats}:\n#{(changed_specs + broken_specs).join("\n")}"
|
61
|
+
exit 1
|
62
|
+
else
|
63
|
+
puts stats
|
64
|
+
end
|
65
|
+
end
|
54
66
|
end
|
55
67
|
end
|
@@ -1,62 +1,59 @@
|
|
1
|
-
require 'tempfile'
|
2
|
-
|
3
1
|
class FactorySloth::CodeMod
|
4
|
-
attr_reader :
|
5
|
-
alias_method :ok?, :ok
|
2
|
+
attr_reader :create_calls, :changed_create_calls, :original_code, :patched_code
|
6
3
|
|
7
4
|
def self.call(code)
|
8
5
|
new(code).tap(&:call)
|
9
6
|
end
|
10
7
|
|
11
8
|
def initialize(code)
|
12
|
-
self.change_count = 0
|
13
9
|
self.original_code = code
|
14
10
|
self.patched_code = code
|
15
11
|
end
|
16
12
|
|
17
13
|
def call
|
18
|
-
create_calls = FactorySloth::CreateCallFinder.call(code: original_code)
|
14
|
+
self.create_calls = FactorySloth::CreateCallFinder.call(code: original_code)
|
19
15
|
|
20
16
|
# Performance note: it might be faster to write ALL possible patches for a
|
21
17
|
# given spec file to tempfiles first, and then run them all in a single
|
22
18
|
# rspec call. However, this would make it impossible to use `--fail-fast`,
|
23
19
|
# and might make examples fail that are not as idempotent as they should be.
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
self.changed_create_calls =
|
21
|
+
create_calls.sort_by { |line, col| [-line, -col] }.select do |line, col|
|
22
|
+
try_patch(line, col, 'build') || try_patch(line, col, 'build_stubbed')
|
23
|
+
end.sort
|
27
24
|
|
28
25
|
# validate whole spec after changes, e.g. to detect side-effects
|
29
|
-
self.ok =
|
30
|
-
self.
|
26
|
+
self.ok = FactorySloth::SpecRunner.call(patched_code)
|
27
|
+
self.changed_create_calls.clear unless ok?
|
31
28
|
self.patched_code = original_code unless ok?
|
32
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
def ok?
|
32
|
+
@ok
|
33
33
|
end
|
34
34
|
|
35
35
|
def changed?
|
36
36
|
change_count > 0
|
37
37
|
end
|
38
38
|
|
39
|
+
def create_count
|
40
|
+
create_calls.count
|
41
|
+
end
|
42
|
+
|
43
|
+
def change_count
|
44
|
+
changed_create_calls.count
|
45
|
+
end
|
46
|
+
|
39
47
|
private
|
40
48
|
|
41
|
-
attr_writer :
|
49
|
+
attr_writer :create_calls, :changed_create_calls, :ok, :original_code, :patched_code
|
42
50
|
|
43
51
|
def try_patch(line, col, variant)
|
44
52
|
new_patched_code =
|
45
53
|
patched_code.sub(/\A(?:.*\n){#{line - 1}}.{#{col}}\Kcreate/, variant)
|
46
|
-
if
|
54
|
+
if FactorySloth::SpecRunner.call(new_patched_code, line: line)
|
47
55
|
puts "- create in line #{line} can be replaced with #{variant}"
|
48
56
|
self.patched_code = new_patched_code
|
49
|
-
self.change_count += 1
|
50
57
|
end
|
51
58
|
end
|
52
|
-
|
53
|
-
def spec_code_passes?(spec_code, line: nil)
|
54
|
-
tempfile = Tempfile.new
|
55
|
-
tempfile.write(spec_code)
|
56
|
-
tempfile.close
|
57
|
-
path = [tempfile.path, line].compact.map(&:to_s).join(':')
|
58
|
-
result = !!system("bundle exec rspec #{path} --fail-fast 1>/dev/null 2>&1")
|
59
|
-
tempfile.unlink
|
60
|
-
result
|
61
|
-
end
|
62
59
|
end
|
@@ -7,29 +7,44 @@ class FactorySloth::CreateCallFinder < Ripper
|
|
7
7
|
new(code).tap(&:parse).locations
|
8
8
|
end
|
9
9
|
|
10
|
-
def initialize(...)
|
10
|
+
def initialize(code, ...)
|
11
11
|
super
|
12
|
+
@code = code
|
13
|
+
@disabled = false
|
12
14
|
@locations = []
|
13
15
|
end
|
14
16
|
private_class_method :new
|
15
17
|
|
18
|
+
def store_location(loc)
|
19
|
+
@locations << loc if loc.instance_of?(Array) && !@disabled
|
20
|
+
end
|
21
|
+
|
16
22
|
def on_ident(name, *)
|
17
23
|
[lineno, column] if %w[create create_list create_pair].include?(name)
|
18
24
|
end
|
19
25
|
|
20
26
|
def on_call(mod, _, loc, *)
|
21
|
-
|
27
|
+
store_location(loc) if mod == 'FactoryBot'
|
22
28
|
end
|
23
29
|
|
24
30
|
def on_command_call(mod, _, loc, *)
|
25
|
-
|
31
|
+
store_location(loc) if mod == 'FactoryBot'
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_comment(text, *)
|
35
|
+
return unless /sloth:(?<directive>disable|enable)/ =~ text
|
36
|
+
|
37
|
+
directive == 'disable' &&
|
38
|
+
@locations.reject! { |loc| loc.first == lineno } ||
|
39
|
+
(@lines ||= @code.lines)[lineno - 1].match?(/^\s*#/) &&
|
40
|
+
(@disabled = directive != 'enable')
|
26
41
|
end
|
27
42
|
|
28
43
|
def on_fcall(loc, *)
|
29
|
-
|
44
|
+
store_location(loc)
|
30
45
|
end
|
31
46
|
|
32
47
|
def on_vcall(loc, *)
|
33
|
-
|
48
|
+
store_location(loc)
|
34
49
|
end
|
35
50
|
end
|
@@ -3,7 +3,7 @@ module FactorySloth
|
|
3
3
|
extend self
|
4
4
|
|
5
5
|
def call(files:, forced_files: [], dry_run: false)
|
6
|
-
files.
|
6
|
+
files.each_with_object({}) do |path, acc|
|
7
7
|
puts "Processing #{path} ..."
|
8
8
|
|
9
9
|
if DoneTracker.done?(path) && !forced_files.include?(path)
|
@@ -11,9 +11,9 @@ module FactorySloth
|
|
11
11
|
next
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
result = process(path, dry_run: dry_run)
|
15
|
+
acc[path] = { ok: result.ok?, changed_create_calls: result.changed_create_calls }
|
15
16
|
DoneTracker.mark_as_done(path)
|
16
|
-
bad_creates_found
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -26,7 +26,7 @@ module FactorySloth
|
|
26
26
|
File.write(path, result.patched_code) if result.changed?
|
27
27
|
end
|
28
28
|
puts result_message(result, dry_run), ''
|
29
|
-
result
|
29
|
+
result
|
30
30
|
end
|
31
31
|
|
32
32
|
def result_message(result, dry_run)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module FactorySloth::SpecRunner
|
4
|
+
def self.call(spec_code, line: nil)
|
5
|
+
tempfile = Tempfile.new
|
6
|
+
tempfile.write(spec_code)
|
7
|
+
tempfile.close
|
8
|
+
path = [tempfile.path, line].compact.map(&:to_s).join(':')
|
9
|
+
result = !!system("bundle exec rspec #{path} --fail-fast 1>/dev/null 2>&1")
|
10
|
+
tempfile.unlink
|
11
|
+
result
|
12
|
+
end
|
13
|
+
end
|
data/lib/factory_sloth.rb
CHANGED
@@ -6,4 +6,5 @@ require_relative 'factory_sloth/create_call_finder'
|
|
6
6
|
require_relative 'factory_sloth/done_tracker'
|
7
7
|
require_relative 'factory_sloth/file_processor'
|
8
8
|
require_relative 'factory_sloth/spec_picker'
|
9
|
+
require_relative 'factory_sloth/spec_runner'
|
9
10
|
require_relative 'factory_sloth/version'
|
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.0
|
4
|
+
version: 1.2.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-
|
11
|
+
date: 2023-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -21,7 +21,6 @@ files:
|
|
21
21
|
- ".rspec"
|
22
22
|
- CHANGELOG.md
|
23
23
|
- Gemfile
|
24
|
-
- Gemfile.lock
|
25
24
|
- LICENSE.txt
|
26
25
|
- README.md
|
27
26
|
- Rakefile
|
@@ -33,6 +32,7 @@ files:
|
|
33
32
|
- lib/factory_sloth/done_tracker.rb
|
34
33
|
- lib/factory_sloth/file_processor.rb
|
35
34
|
- lib/factory_sloth/spec_picker.rb
|
35
|
+
- lib/factory_sloth/spec_runner.rb
|
36
36
|
- lib/factory_sloth/version.rb
|
37
37
|
homepage: https://github.com/jaynetics/factory_sloth
|
38
38
|
licenses:
|
data/Gemfile.lock
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
factory_sloth (1.0.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
diff-lcs (1.5.0)
|
10
|
-
docile (1.4.0)
|
11
|
-
rake (13.0.6)
|
12
|
-
rexml (3.2.5)
|
13
|
-
rspec (3.12.0)
|
14
|
-
rspec-core (~> 3.12.0)
|
15
|
-
rspec-expectations (~> 3.12.0)
|
16
|
-
rspec-mocks (~> 3.12.0)
|
17
|
-
rspec-core (3.12.2)
|
18
|
-
rspec-support (~> 3.12.0)
|
19
|
-
rspec-expectations (3.12.3)
|
20
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
-
rspec-support (~> 3.12.0)
|
22
|
-
rspec-mocks (3.12.5)
|
23
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
-
rspec-support (~> 3.12.0)
|
25
|
-
rspec-support (3.12.0)
|
26
|
-
simplecov (0.22.0)
|
27
|
-
docile (~> 1.1)
|
28
|
-
simplecov-html (~> 0.11)
|
29
|
-
simplecov_json_formatter (~> 0.1)
|
30
|
-
simplecov-cobertura (2.1.0)
|
31
|
-
rexml
|
32
|
-
simplecov (~> 0.19)
|
33
|
-
simplecov-html (0.12.3)
|
34
|
-
simplecov_json_formatter (0.1.4)
|
35
|
-
|
36
|
-
PLATFORMS
|
37
|
-
arm64-darwin-21
|
38
|
-
ruby
|
39
|
-
|
40
|
-
DEPENDENCIES
|
41
|
-
factory_sloth!
|
42
|
-
rake (~> 13.0)
|
43
|
-
rspec (~> 3.0)
|
44
|
-
simplecov-cobertura (~> 2.1)
|
45
|
-
|
46
|
-
BUNDLED WITH
|
47
|
-
2.4.7
|