Skip to content

Commit 152465f

Browse files
committed
Upgrade to unparser 0.7.x interface
1 parent 147b9fe commit 152465f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+894
-401
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,6 @@ Style/RedundantHeredocDelimiterQuotes:
193193
# Bad advice, as rubygems.org cannot see dev dependencies anymore
194194
Gemspec/DevelopmentDependencies:
195195
Enabled: false
196+
# Useful for code structure
197+
Lint/UselessConstantScoping:
198+
Enabled: false

Changelog.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
# v0.13.0 unreleased
2+
3+
Significant unparser upgrade. Mutant now:
4+
5+
Avoids emitting mutations that do not round trip against unparsers API.
6+
7+
This change generates less mutations, and currently slightly increases boot time.
8+
But the number of mutations that are hitting the test suite is much lower so it
9+
should balance.
10+
11+
Also its a good step towards parallel mutation generation reducing boot times.
12+
13+
Also mutations that are currently not creating round trippable ASTS are removed:
14+
15+
* Negation of `if` conditions, as negating these needs operator precendence sensitive AST
16+
mutations mutant does not have the infrastructure for right now. The mutation to `unless` is
17+
still present so there is no real reduction of semantic coverage.
18+
19+
* All mutations that modify the local variable scope are removed. These generated ASTs that would
20+
not round trip and are thus likely to be covered, execution wise these would move lvar reads to
21+
implicit self receivers in the past. But this was not intended by these mutations, at least not
22+
without explicitly changing the reads to send nodes explicitly.
23+
124
# v0.12.5 unreleased
225

326
* [#1458](https://github.com/mbj/mutant/pull/1458)

Gemfile.lock

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
GIT
2+
remote: https://github.com/mbj/unparser
3+
revision: 5751efd6e791952890e8a23b90538cc0367f4069
4+
ref: 5751efd6e791952890e8a23b90538cc0367f4069
5+
specs:
6+
unparser (0.7.0)
7+
diff-lcs (~> 1.6)
8+
parser (>= 3.3.0)
9+
110
PATH
211
remote: .
312
specs:
@@ -6,68 +15,69 @@ PATH
615
parser (~> 3.3.0)
716
regexp_parser (~> 2.9.0)
817
sorbet-runtime (~> 0.5.0)
9-
unparser (~> 0.6.14)
18+
unparser (~> 0.7.0)
1019

1120
GEM
1221
remote: https://rubygems.org/
1322
specs:
1423
ast (2.4.2)
15-
diff-lcs (1.5.1)
16-
json (2.7.2)
17-
language_server-protocol (3.17.0.3)
18-
parallel (1.25.1)
19-
parser (3.3.2.0)
24+
diff-lcs (1.6.0)
25+
json (2.10.2)
26+
language_server-protocol (3.17.0.4)
27+
lint_roller (1.1.0)
28+
parallel (1.26.3)
29+
parser (3.3.7.1)
2030
ast (~> 2.4.1)
2131
racc
22-
racc (1.8.0)
32+
racc (1.8.1)
2333
rainbow (3.1.1)
24-
regexp_parser (2.9.2)
25-
rexml (3.3.9)
34+
regexp_parser (2.9.3)
2635
rspec (3.13.0)
2736
rspec-core (~> 3.13.0)
2837
rspec-expectations (~> 3.13.0)
2938
rspec-mocks (~> 3.13.0)
30-
rspec-core (3.13.0)
39+
rspec-core (3.13.3)
3140
rspec-support (~> 3.13.0)
32-
rspec-expectations (3.13.0)
41+
rspec-expectations (3.13.3)
3342
diff-lcs (>= 1.2.0, < 2.0)
3443
rspec-support (~> 3.13.0)
35-
rspec-its (1.3.0)
44+
rspec-its (1.3.1)
3645
rspec-core (>= 3.0.0)
3746
rspec-expectations (>= 3.0.0)
38-
rspec-mocks (3.13.1)
47+
rspec-mocks (3.13.2)
3948
diff-lcs (>= 1.2.0, < 2.0)
4049
rspec-support (~> 3.13.0)
41-
rspec-support (3.13.1)
42-
rubocop (1.64.1)
50+
rspec-support (3.13.2)
51+
rubocop (1.74.0)
4352
json (~> 2.3)
44-
language_server-protocol (>= 3.17.0)
53+
language_server-protocol (~> 3.17.0.2)
54+
lint_roller (~> 1.1.0)
4555
parallel (~> 1.10)
4656
parser (>= 3.3.0.2)
4757
rainbow (>= 2.2.2, < 4.0)
48-
regexp_parser (>= 1.8, < 3.0)
49-
rexml (>= 3.2.5, < 4.0)
50-
rubocop-ast (>= 1.31.1, < 2.0)
58+
regexp_parser (>= 2.9.3, < 3.0)
59+
rubocop-ast (>= 1.38.0, < 2.0)
5160
ruby-progressbar (~> 1.7)
52-
unicode-display_width (>= 2.4.0, < 3.0)
53-
rubocop-ast (1.31.3)
61+
unicode-display_width (>= 2.4.0, < 4.0)
62+
rubocop-ast (1.38.1)
5463
parser (>= 3.3.1.0)
5564
ruby-progressbar (1.13.0)
56-
sorbet-runtime (0.5.11422)
57-
unicode-display_width (2.5.0)
58-
unparser (0.6.14)
59-
diff-lcs (~> 1.3)
60-
parser (>= 3.3.0)
65+
sorbet-runtime (0.5.11934)
66+
unicode-display_width (3.1.4)
67+
unicode-emoji (~> 4.0, >= 4.0.4)
68+
unicode-emoji (4.0.4)
6169

6270
PLATFORMS
6371
ruby
72+
x86_64-linux
6473

6574
DEPENDENCIES
6675
mutant!
6776
rspec (~> 3.10)
6877
rspec-core (~> 3.10)
6978
rspec-its (~> 1.3.0)
7079
rubocop (~> 1.7)
80+
unparser!
7181

7282
BUNDLED WITH
73-
2.5.6
83+
2.5.22

Gemfile.shared

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gem 'unparser', git: 'https://github.com/mbj/unparser', ref: '5751efd6e791952890e8a23b90538cc0367f4069'

lib/mutant/cli/command/util.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ def action
2020
end
2121
end
2222

23-
@targets.each(&method(:print_mutations))
24-
Either::Right.new(nil)
23+
if @targets.map(&method(:print_mutations)).all?
24+
Either::Right.new(nil)
25+
else
26+
Either::Left.new('Invalid mutation detected!')
27+
end
2528
end
2629

2730
private
@@ -70,18 +73,28 @@ def add_target_options(parser)
7073
end
7174
end
7275

76+
# rubocop:disable Metrics/MethodLength
7377
def print_mutations(target)
7478
world.stdout.puts(target.identification)
7579

80+
success = true
81+
7682
Mutator::Node.mutate(
7783
config: Mutant::Mutation::Config::DEFAULT.with(ignore_patterns: @ignore_patterns),
7884
node: target.node
7985
).each do |mutation|
80-
Reporter::CLI::Printer::Mutation.call(
81-
object: Mutant::Mutation::Evil.new(subject: target, node: mutation),
82-
output: world.stdout
86+
Mutant::Mutation::Evil.from_node(subject: target, node: mutation).either(
87+
->(violation) { world.stdout.puts(violation.report); success = false },
88+
lambda { |object|
89+
Reporter::CLI::Printer::Mutation.call(
90+
object:,
91+
output: world.stdout
92+
)
93+
}
8394
)
8495
end
96+
97+
success
8598
end
8699

87100
def parse_remaining_arguments(arguments)

lib/mutant/meta/example.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Expected
2323
#
2424
# @return [Verification]
2525
def verification
26-
Verification.new(example: self, mutations: generated)
26+
Verification.from_mutations(example: self, mutations: generated)
2727
end
2828
memoize :verification
2929

@@ -45,13 +45,13 @@ def context
4545
)
4646
end
4747

48-
# Original source as generated by unparser
48+
# Original source
4949
#
5050
# @return [String]
51-
def original_source_generated
51+
def source
5252
Unparser.unparse(node)
5353
end
54-
memoize :original_source_generated
54+
memoize :source
5555

5656
# Generated mutations on example source
5757
#
@@ -61,7 +61,7 @@ def generated
6161
config: Mutation::Config::DEFAULT.with(operators:),
6262
node:
6363
).map do |node|
64-
Mutation::Evil.new(subject: self, node:)
64+
Mutation::Evil.from_node(subject: self, node:)
6565
end
6666
end
6767
memoize :generated

lib/mutant/meta/example/verification.rb

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,31 @@
22

33
module Mutant
44
module Meta
5+
# rubocop:disable Metrics/ClassLength
56
class Example
67
# Example verification
78
class Verification
8-
include Adamantium, Anima.new(:example, :mutations)
9+
include Adamantium, Anima.new(:example, :invalid, :valid)
10+
11+
def self.from_mutations(example:, mutations:)
12+
valid, invalid = [], []
13+
14+
mutations.each do |mutation|
15+
mutation.either(invalid.public_method(:<<), valid.public_method(:<<))
16+
end
17+
18+
new(example:, invalid:, valid:)
19+
end
920

1021
# Test if mutation was verified successfully
1122
#
1223
# @return [Boolean]
1324
def success?
1425
[
15-
original_verification,
16-
invalid,
26+
invalid_report,
1727
missing,
1828
no_diffs,
29+
original_verification_report,
1930
unexpected
2031
].all?(&:empty?)
2132
end
@@ -29,12 +40,12 @@ def error_report
2940

3041
def reports
3142
reports = [example.location]
32-
reports.concat(original)
33-
reports.concat(original_verification)
43+
reports.concat(original_report)
44+
reports.concat(original_verification_report)
3445
reports.concat(make_report('Missing mutations:', missing))
3546
reports.concat(make_report('Unexpected mutations:', unexpected))
3647
reports.concat(make_report('No-Diff mutations:', no_diffs))
37-
reports.concat(invalid)
48+
reports.concat(invalid_report)
3849
end
3950

4051
def make_report(label, mutations)
@@ -52,15 +63,15 @@ def report_mutation(mutation)
5263
]
5364
end
5465

55-
def original
66+
def original_report
5667
[
5768
"Original: (operators: #{example.operators.class.operators_name})",
5869
example.node,
5970
example.original_source
6071
]
6172
end
6273

63-
def original_verification
74+
def original_verification_report
6475
validation = Unparser::Validation.from_string(example.original_source)
6576
if validation.success?
6677
[]
@@ -77,30 +88,34 @@ def prefix(prefix, string)
7788
end.join
7889
end
7990

80-
def invalid
81-
mutations.each_with_object([]) do |mutation, aggregate|
82-
validation = Unparser::Validation.from_node(mutation.node)
83-
aggregate << prefix('[invalid-mutation]', validation.report) unless validation.success?
91+
def invalid_report
92+
invalid.map do |validation|
93+
prefix('[invalid-mutation]', validation.report)
8494
end
8595
end
86-
memoize :invalid
96+
memoize :invalid_report
8797

8898
def unexpected
89-
mutations.reject do |mutation|
99+
valid.reject do |mutation|
90100
example.expected.any? { |expected| expected.node.eql?(mutation.node) }
91101
end
92102
end
93103
memoize :unexpected
94104

95105
def missing
96-
(example.expected.map(&:node) - mutations.map(&:node)).map do |node|
97-
Mutation::Evil.new(subject: example, node:)
106+
example.expected.each_with_object([]) do |expected, aggregate|
107+
next if valid.any? { |mutation| expected.node.eql?(mutation.node) }
108+
aggregate << Mutation::Evil.new(
109+
node: expected.node,
110+
source: expected.original_source,
111+
subject: example
112+
)
98113
end
99114
end
100115
memoize :missing
101116

102117
def no_diffs
103-
mutations.select { |mutation| mutation.source.eql?(example.original_source_generated) }
118+
valid.select { |mutation| mutation.source.eql?(example.source) }
104119
end
105120
memoize :no_diffs
106121

0 commit comments

Comments
 (0)