evilution 0.18.0 → 0.20.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.migration-hint-ts +1 -1
  3. data/.beads/issues.jsonl +85 -15
  4. data/CHANGELOG.md +37 -0
  5. data/README.md +24 -4
  6. data/lib/evilution/baseline.rb +9 -1
  7. data/lib/evilution/cli.rb +11 -0
  8. data/lib/evilution/equivalent/detector.rb +3 -1
  9. data/lib/evilution/equivalent/heuristic/alias_swap.rb +2 -1
  10. data/lib/evilution/equivalent/heuristic/void_context.rb +77 -0
  11. data/lib/evilution/integration/crash_detector.rb +55 -0
  12. data/lib/evilution/integration/rspec.rb +102 -6
  13. data/lib/evilution/isolation/fork.rb +10 -6
  14. data/lib/evilution/isolation/in_process.rb +14 -10
  15. data/lib/evilution/mutator/operator/begin_unwrap.rb +21 -0
  16. data/lib/evilution/mutator/operator/block_param_removal.rb +57 -0
  17. data/lib/evilution/mutator/operator/case_when.rb +55 -0
  18. data/lib/evilution/mutator/operator/equality_to_identity.rb +22 -0
  19. data/lib/evilution/mutator/operator/lambda_body.rb +18 -0
  20. data/lib/evilution/mutator/operator/loop_flip.rb +27 -0
  21. data/lib/evilution/mutator/operator/method_body_replacement.rb +10 -6
  22. data/lib/evilution/mutator/operator/predicate_replacement.rb +27 -0
  23. data/lib/evilution/mutator/operator/retry_removal.rb +16 -0
  24. data/lib/evilution/mutator/operator/send_mutation.rb +8 -1
  25. data/lib/evilution/mutator/operator/string_interpolation.rb +32 -0
  26. data/lib/evilution/mutator/registry.rb +10 -1
  27. data/lib/evilution/parallel/pool.rb +2 -2
  28. data/lib/evilution/parallel/work_queue.rb +54 -13
  29. data/lib/evilution/related_spec_heuristic.rb +63 -0
  30. data/lib/evilution/reporter/cli.rb +14 -8
  31. data/lib/evilution/reporter/html.rb +32 -2
  32. data/lib/evilution/reporter/json.rb +15 -0
  33. data/lib/evilution/result/coverage_gap.rb +35 -0
  34. data/lib/evilution/result/coverage_gap_grouper.rb +22 -0
  35. data/lib/evilution/result/mutation_result.rb +5 -2
  36. data/lib/evilution/result/summary.rb +5 -0
  37. data/lib/evilution/runner.rb +7 -4
  38. data/lib/evilution/session/store.rb +13 -0
  39. data/lib/evilution/spec_resolver.rb +13 -1
  40. data/lib/evilution/version.rb +1 -1
  41. data/lib/evilution.rb +9 -0
  42. data/script/memory_check +22 -0
  43. metadata +16 -2
@@ -325,7 +325,7 @@ class Evilution::Runner
325
325
 
326
326
  def run_mutations_parallel(mutations, baseline_result = nil)
327
327
  integration = build_integration
328
- pool = Evilution::Parallel::Pool.new(size: config.jobs, hooks: @hooks)
328
+ pool = Evilution::Parallel::Pool.new(size: config.jobs, hooks: @hooks, item_timeout: config.timeout ? config.timeout * 2 : nil)
329
329
  worker_isolator = Evilution::Isolation::InProcess.new
330
330
  spec_resolver = baseline_result&.failed? ? Evilution::SpecResolver.new : nil
331
331
  state = { results: [], survived_count: 0, truncated: false, completed: 0 }
@@ -396,7 +396,8 @@ class Evilution::Runner
396
396
  duration: result.duration,
397
397
  test_command: result.test_command,
398
398
  child_rss_kb: result.child_rss_kb,
399
- memory_delta_kb: result.memory_delta_kb
399
+ memory_delta_kb: result.memory_delta_kb,
400
+ parent_rss_kb: result.parent_rss_kb
400
401
  )
401
402
  end
402
403
 
@@ -407,7 +408,8 @@ class Evilution::Runner
407
408
  killing_test: result.killing_test,
408
409
  test_command: result.test_command,
409
410
  child_rss_kb: result.child_rss_kb,
410
- memory_delta_kb: result.memory_delta_kb
411
+ memory_delta_kb: result.memory_delta_kb,
412
+ parent_rss_kb: result.parent_rss_kb
411
413
  }
412
414
  end
413
415
 
@@ -420,7 +422,8 @@ class Evilution::Runner
420
422
  killing_test: data[:killing_test],
421
423
  test_command: data[:test_command],
422
424
  child_rss_kb: data[:child_rss_kb],
423
- memory_delta_kb: data[:memory_delta_kb]
425
+ memory_delta_kb: data[:memory_delta_kb],
426
+ parent_rss_kb: data[:parent_rss_kb]
424
427
  )
425
428
  end
426
429
  end
@@ -65,6 +65,7 @@ class Evilution::Session::Store
65
65
  git: git_context,
66
66
  summary: build_summary(summary),
67
67
  survived: summary.survived_results.map { |r| build_mutation_detail(r) },
68
+ coverage_gaps: build_coverage_gaps(summary),
68
69
  killed_count: summary.killed,
69
70
  timed_out_count: summary.timed_out,
70
71
  error_count: summary.errors,
@@ -101,6 +102,18 @@ class Evilution::Session::Store
101
102
  }
102
103
  end
103
104
 
105
+ def build_coverage_gaps(summary)
106
+ summary.coverage_gaps.map do |gap|
107
+ {
108
+ file: gap.file_path,
109
+ subject: gap.subject_name,
110
+ line: gap.line,
111
+ operators: gap.operator_names,
112
+ count: gap.count
113
+ }
114
+ end
115
+ end
116
+
104
117
  def git_context
105
118
  sha = `git rev-parse HEAD 2>/dev/null`.strip
106
119
  branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Evilution::SpecResolver
4
4
  STRIPPABLE_PREFIXES = %w[lib/ app/].freeze
5
+ CONTROLLER_PREFIX = "controllers/"
5
6
 
6
7
  def call(source_path)
7
8
  return nil if source_path.nil? || source_path.empty?
@@ -29,7 +30,8 @@ class Evilution::SpecResolver
29
30
 
30
31
  candidates = if prefix
31
32
  stripped = base.delete_prefix(prefix)
32
- ["spec/#{stripped}", "spec/#{base}"]
33
+ request_spec = controller_to_request_spec(stripped)
34
+ [request_spec, "spec/#{stripped}", "spec/#{base}"].compact
33
35
  else
34
36
  ["spec/#{base}"]
35
37
  end
@@ -39,6 +41,16 @@ class Evilution::SpecResolver
39
41
  candidates + fallbacks
40
42
  end
41
43
 
44
+ def controller_to_request_spec(stripped_path)
45
+ return nil unless stripped_path.start_with?(CONTROLLER_PREFIX)
46
+ return nil unless stripped_path.end_with?("_controller_spec.rb")
47
+
48
+ request_path = stripped_path
49
+ .delete_prefix(CONTROLLER_PREFIX)
50
+ .sub(/_controller_spec\.rb\z/, "_spec.rb")
51
+ "spec/requests/#{request_path}"
52
+ end
53
+
42
54
  def parent_fallback_candidates(spec_path)
43
55
  parts = spec_path.split("/")
44
56
  # parts: ["spec", "foo", "bar_spec.rb"] — need at least 3 parts for fallback
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.18.0"
4
+ VERSION = "0.20.0"
5
5
  end
data/lib/evilution.rb CHANGED
@@ -81,6 +81,15 @@ require_relative "evilution/mutator/operator/yield_statement"
81
81
  require_relative "evilution/mutator/operator/splat_operator"
82
82
  require_relative "evilution/mutator/operator/defined_check"
83
83
  require_relative "evilution/mutator/operator/regex_capture"
84
+ require_relative "evilution/mutator/operator/loop_flip"
85
+ require_relative "evilution/mutator/operator/string_interpolation"
86
+ require_relative "evilution/mutator/operator/retry_removal"
87
+ require_relative "evilution/mutator/operator/case_when"
88
+ require_relative "evilution/mutator/operator/predicate_replacement"
89
+ require_relative "evilution/mutator/operator/equality_to_identity"
90
+ require_relative "evilution/mutator/operator/lambda_body"
91
+ require_relative "evilution/mutator/operator/begin_unwrap"
92
+ require_relative "evilution/mutator/operator/block_param_removal"
84
93
  require_relative "evilution/mutator/registry"
85
94
  require_relative "evilution/equivalent"
86
95
  require_relative "evilution/equivalent/heuristic"
data/script/memory_check CHANGED
@@ -3,8 +3,12 @@
3
3
 
4
4
  require_relative "../lib/evilution"
5
5
  require_relative "../lib/evilution/memory/leak_check"
6
+ require_relative "../lib/evilution/integration/rspec"
6
7
 
7
8
  FIXTURE = File.expand_path("../spec/support/fixtures/simple_class.rb", __dir__)
9
+ FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/simple_class_spec.rb", __dir__)
10
+ COMPLEX_FIXTURE = File.expand_path("../lib/evilution/config.rb", __dir__)
11
+ COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/evilution/config_spec.rb", __dir__)
8
12
  ITERATIONS = Integer(ENV.fetch("MEMORY_CHECK_ITERATIONS", 50))
9
13
  MAX_GROWTH_KB = Integer(ENV.fetch("MEMORY_CHECK_MAX_GROWTH_KB", 10_240))
10
14
 
@@ -90,5 +94,23 @@ if mutations.size >= 2
90
94
  end
91
95
  end
92
96
 
97
+ # 5. RSpec integration per-mutation with complex fixture
98
+ # Uses Config (227 LOC, 73 specs, 564 mutations) for realistic per-mutation load:
99
+ # more ExampleGroup subclasses, deeper spec nesting, heavier metadata.
100
+ complex_parser = Evilution::AST::Parser.new
101
+ complex_registry = Evilution::Mutator::Registry.default
102
+ complex_subjects = complex_parser.call(COMPLEX_FIXTURE)
103
+ complex_mutations = complex_subjects.flat_map { |s| complex_registry.mutations_for(s) }
104
+
105
+ integration = Evilution::Integration::RSpec.new(test_files: [COMPLEX_FIXTURE_SPEC])
106
+
107
+ all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 20_480) do
108
+ mutation = complex_mutations.sample
109
+ result = integration.call(mutation)
110
+ raise "RSpec integration memory check failed: #{result[:error]}" if result[:error]
111
+
112
+ result
113
+ end
114
+
93
115
  puts all_passed ? "All memory checks passed." : "Some memory checks failed!"
94
116
  exit(all_passed ? 0 : 1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evilution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Kiselev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-03 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -102,6 +102,7 @@ files:
102
102
  - lib/evilution/equivalent/heuristic/dead_code.rb
103
103
  - lib/evilution/equivalent/heuristic/method_body_nil.rb
104
104
  - lib/evilution/equivalent/heuristic/noop_source.rb
105
+ - lib/evilution/equivalent/heuristic/void_context.rb
105
106
  - lib/evilution/git.rb
106
107
  - lib/evilution/git/changed_files.rb
107
108
  - lib/evilution/hooks.rb
@@ -109,6 +110,7 @@ files:
109
110
  - lib/evilution/hooks/registry.rb
110
111
  - lib/evilution/integration.rb
111
112
  - lib/evilution/integration/base.rb
113
+ - lib/evilution/integration/crash_detector.rb
112
114
  - lib/evilution/integration/rspec.rb
113
115
  - lib/evilution/isolation.rb
114
116
  - lib/evilution/isolation/fork.rb
@@ -130,12 +132,15 @@ files:
130
132
  - lib/evilution/mutator/operator/arithmetic_replacement.rb
131
133
  - lib/evilution/mutator/operator/array_literal.rb
132
134
  - lib/evilution/mutator/operator/bang_method.rb
135
+ - lib/evilution/mutator/operator/begin_unwrap.rb
133
136
  - lib/evilution/mutator/operator/bitwise_complement.rb
134
137
  - lib/evilution/mutator/operator/bitwise_replacement.rb
138
+ - lib/evilution/mutator/operator/block_param_removal.rb
135
139
  - lib/evilution/mutator/operator/block_removal.rb
136
140
  - lib/evilution/mutator/operator/boolean_literal_replacement.rb
137
141
  - lib/evilution/mutator/operator/boolean_operator_replacement.rb
138
142
  - lib/evilution/mutator/operator/break_statement.rb
143
+ - lib/evilution/mutator/operator/case_when.rb
139
144
  - lib/evilution/mutator/operator/class_variable_write.rb
140
145
  - lib/evilution/mutator/operator/collection_replacement.rb
141
146
  - lib/evilution/mutator/operator/collection_return.rb
@@ -146,6 +151,7 @@ files:
146
151
  - lib/evilution/mutator/operator/conditional_negation.rb
147
152
  - lib/evilution/mutator/operator/defined_check.rb
148
153
  - lib/evilution/mutator/operator/ensure_removal.rb
154
+ - lib/evilution/mutator/operator/equality_to_identity.rb
149
155
  - lib/evilution/mutator/operator/explicit_super_mutation.rb
150
156
  - lib/evilution/mutator/operator/float_literal.rb
151
157
  - lib/evilution/mutator/operator/global_variable_write.rb
@@ -157,7 +163,9 @@ files:
157
163
  - lib/evilution/mutator/operator/instance_variable_write.rb
158
164
  - lib/evilution/mutator/operator/integer_literal.rb
159
165
  - lib/evilution/mutator/operator/keyword_argument.rb
166
+ - lib/evilution/mutator/operator/lambda_body.rb
160
167
  - lib/evilution/mutator/operator/local_variable_assignment.rb
168
+ - lib/evilution/mutator/operator/loop_flip.rb
161
169
  - lib/evilution/mutator/operator/method_body_replacement.rb
162
170
  - lib/evilution/mutator/operator/method_call_removal.rb
163
171
  - lib/evilution/mutator/operator/mixin_removal.rb
@@ -168,6 +176,7 @@ files:
168
176
  - lib/evilution/mutator/operator/pattern_matching_alternative.rb
169
177
  - lib/evilution/mutator/operator/pattern_matching_array.rb
170
178
  - lib/evilution/mutator/operator/pattern_matching_guard.rb
179
+ - lib/evilution/mutator/operator/predicate_replacement.rb
171
180
  - lib/evilution/mutator/operator/range_replacement.rb
172
181
  - lib/evilution/mutator/operator/receiver_replacement.rb
173
182
  - lib/evilution/mutator/operator/redo_statement.rb
@@ -175,11 +184,13 @@ files:
175
184
  - lib/evilution/mutator/operator/regexp_mutation.rb
176
185
  - lib/evilution/mutator/operator/rescue_body_replacement.rb
177
186
  - lib/evilution/mutator/operator/rescue_removal.rb
187
+ - lib/evilution/mutator/operator/retry_removal.rb
178
188
  - lib/evilution/mutator/operator/return_value_removal.rb
179
189
  - lib/evilution/mutator/operator/scalar_return.rb
180
190
  - lib/evilution/mutator/operator/send_mutation.rb
181
191
  - lib/evilution/mutator/operator/splat_operator.rb
182
192
  - lib/evilution/mutator/operator/statement_deletion.rb
193
+ - lib/evilution/mutator/operator/string_interpolation.rb
183
194
  - lib/evilution/mutator/operator/string_literal.rb
184
195
  - lib/evilution/mutator/operator/superclass_removal.rb
185
196
  - lib/evilution/mutator/operator/symbol_literal.rb
@@ -189,6 +200,7 @@ files:
189
200
  - lib/evilution/parallel.rb
190
201
  - lib/evilution/parallel/pool.rb
191
202
  - lib/evilution/parallel/work_queue.rb
203
+ - lib/evilution/related_spec_heuristic.rb
192
204
  - lib/evilution/reporter.rb
193
205
  - lib/evilution/reporter/cli.rb
194
206
  - lib/evilution/reporter/html.rb
@@ -196,6 +208,8 @@ files:
196
208
  - lib/evilution/reporter/progress_bar.rb
197
209
  - lib/evilution/reporter/suggestion.rb
198
210
  - lib/evilution/result.rb
211
+ - lib/evilution/result/coverage_gap.rb
212
+ - lib/evilution/result/coverage_gap_grouper.rb
199
213
  - lib/evilution/result/mutation_result.rb
200
214
  - lib/evilution/result/summary.rb
201
215
  - lib/evilution/runner.rb