evilution 1.2.0 → 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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +38 -0
  3. data/.rubocop_todo.yml +9 -0
  4. data/CHANGELOG.md +47 -0
  5. data/README.md +65 -7
  6. data/docs/architecture.md +35 -1
  7. data/lib/evilution/ast/parser.rb +1 -0
  8. data/lib/evilution/ast/pattern/filter.rb +1 -0
  9. data/lib/evilution/ast/pattern/parser.rb +1 -0
  10. data/lib/evilution/ast/pattern.rb +2 -0
  11. data/lib/evilution/ast/sorbet_sig_detector.rb +1 -0
  12. data/lib/evilution/baseline.rb +37 -15
  13. data/lib/evilution/child_output.rb +1 -1
  14. data/lib/evilution/cli/command.rb +1 -0
  15. data/lib/evilution/cli/commands/tests_list.rb +4 -3
  16. data/lib/evilution/cli/commands.rb +2 -0
  17. data/lib/evilution/cli/dispatcher.rb +2 -0
  18. data/lib/evilution/cli/exit_guard.rb +2 -0
  19. data/lib/evilution/cli/parsed_args.rb +2 -0
  20. data/lib/evilution/cli/parser/command_extractor.rb +2 -0
  21. data/lib/evilution/cli/parser/file_args.rb +2 -0
  22. data/lib/evilution/cli/parser/options_builder.rb +3 -0
  23. data/lib/evilution/cli/parser/stdin_reader.rb +1 -0
  24. data/lib/evilution/cli/parser.rb +1 -0
  25. data/lib/evilution/cli/printers/tests_list.rb +5 -4
  26. data/lib/evilution/cli/printers.rb +2 -0
  27. data/lib/evilution/cli/result.rb +2 -0
  28. data/lib/evilution/cli.rb +5 -0
  29. data/lib/evilution/config/builders.rb +2 -0
  30. data/lib/evilution/config/env_loader.rb +2 -0
  31. data/lib/evilution/config/file_loader.rb +1 -0
  32. data/lib/evilution/config/sources.rb +1 -0
  33. data/lib/evilution/config/validators/example_targeting_cache.rb +1 -0
  34. data/lib/evilution/config/validators/example_targeting_fallback.rb +1 -0
  35. data/lib/evilution/config/validators/example_targeting_strategy.rb +1 -0
  36. data/lib/evilution/config/validators/fail_fast.rb +1 -0
  37. data/lib/evilution/config/validators/hooks.rb +1 -0
  38. data/lib/evilution/config/validators/ignore_patterns.rb +1 -0
  39. data/lib/evilution/config/validators/integration.rb +1 -0
  40. data/lib/evilution/config/validators/isolation.rb +1 -0
  41. data/lib/evilution/config/validators/jobs.rb +1 -0
  42. data/lib/evilution/config/validators/preload.rb +1 -0
  43. data/lib/evilution/config/validators/profile.rb +1 -0
  44. data/lib/evilution/config/validators/spec_mappings.rb +1 -0
  45. data/lib/evilution/config/validators/spec_pattern.rb +1 -0
  46. data/lib/evilution/config/validators/warmup.rb +17 -0
  47. data/lib/evilution/config/validators.rb +2 -0
  48. data/lib/evilution/config.rb +4 -3
  49. data/lib/evilution/coverage.rb +1 -1
  50. data/lib/evilution/coverage_example_filter.rb +1 -1
  51. data/lib/evilution/diagnostic.rb +1 -1
  52. data/lib/evilution/example_filter.rb +1 -1
  53. data/lib/evilution/integration/loading/body_call_neutralizer.rb +10 -2
  54. data/lib/evilution/integration/loading/test_load_path.rb +21 -13
  55. data/lib/evilution/integration/rspec/state_guard/configuration_state.rb +1 -0
  56. data/lib/evilution/integration/rspec/state_guard/configuration_streams.rb +1 -0
  57. data/lib/evilution/integration/rspec/state_guard/example_groups_constants.rb +1 -2
  58. data/lib/evilution/integration/rspec/state_guard/internals.rb +1 -2
  59. data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +1 -2
  60. data/lib/evilution/integration/rspec/state_guard/reporter_arrays.rb +1 -0
  61. data/lib/evilution/integration/rspec/state_guard/world_example_groups.rb +1 -0
  62. data/lib/evilution/integration/rspec/state_guard/world_filtered_examples.rb +1 -0
  63. data/lib/evilution/integration/rspec/state_guard/world_sources_by_path.rb +1 -0
  64. data/lib/evilution/integration/rspec/state_guard.rb +6 -0
  65. data/lib/evilution/mcp/info_tool/actions/environment.rb +1 -0
  66. data/lib/evilution/mcp/info_tool/actions/feedback.rb +1 -0
  67. data/lib/evilution/mcp/info_tool/actions/statuses.rb +1 -0
  68. data/lib/evilution/mcp/info_tool/actions/subjects.rb +1 -0
  69. data/lib/evilution/mcp/info_tool/actions/tests.rb +1 -0
  70. data/lib/evilution/mutation.rb +1 -1
  71. data/lib/evilution/mutator/operator/argument_list_removal.rb +52 -0
  72. data/lib/evilution/mutator/operator/argument_propagation.rb +54 -0
  73. data/lib/evilution/mutator/operator/array_coercion_to_literal.rb +34 -0
  74. data/lib/evilution/mutator/operator/attribute_write_to_read.rb +34 -0
  75. data/lib/evilution/mutator/operator/bang_method.rb +11 -3
  76. data/lib/evilution/mutator/operator/binary_operand_promotion.rb +63 -0
  77. data/lib/evilution/mutator/operator/block_body_promotion.rb +45 -0
  78. data/lib/evilution/mutator/operator/block_body_to_nil.rb +52 -0
  79. data/lib/evilution/mutator/operator/block_body_to_raise.rb +34 -0
  80. data/lib/evilution/mutator/operator/call_to_nil.rb +34 -0
  81. data/lib/evilution/mutator/operator/coercion_emptying.rb +60 -0
  82. data/lib/evilution/mutator/operator/collection_replacement.rb +19 -9
  83. data/lib/evilution/mutator/operator/comparison_replacement.rb +37 -12
  84. data/lib/evilution/mutator/operator/const_get_to_constant_path.rb +50 -0
  85. data/lib/evilution/mutator/operator/dig_to_fetch_chain.rb +45 -0
  86. data/lib/evilution/mutator/operator/double_negation_removal.rb +22 -0
  87. data/lib/evilution/mutator/operator/dynamic_dispatch_resolution.rb +79 -0
  88. data/lib/evilution/mutator/operator/inequality_to_negated_identity.rb +43 -0
  89. data/lib/evilution/mutator/operator/keyword_argument_removal.rb +47 -0
  90. data/lib/evilution/mutator/operator/proc_to_lambda.rb +43 -0
  91. data/lib/evilution/mutator/operator/receiver_constructor_swap.rb +51 -0
  92. data/lib/evilution/mutator/operator/reduce_to_sum.rb +58 -0
  93. data/lib/evilution/mutator/operator/regexp_anchor_to_predicate.rb +136 -0
  94. data/lib/evilution/mutator/operator/safe_navigation_removal.rb +67 -0
  95. data/lib/evilution/mutator/operator/send_mutation.rb +42 -6
  96. data/lib/evilution/mutator/operator/symbol_to_proc_replacement.rb +59 -0
  97. data/lib/evilution/mutator/operator/to_i_to_integer.rb +38 -0
  98. data/lib/evilution/mutator/primitives.rb +25 -0
  99. data/lib/evilution/mutator/registry.rb +24 -1
  100. data/lib/evilution/parallel/pool.rb +1 -0
  101. data/lib/evilution/parallel_db_warning.rb +1 -1
  102. data/lib/evilution/rails_warmup.rb +61 -0
  103. data/lib/evilution/reporter/html/report.rb +1 -0
  104. data/lib/evilution/result/coverage_gap_grouper.rb +1 -0
  105. data/lib/evilution/result/subject_scorer.rb +1 -0
  106. data/lib/evilution/runner/baseline_runner.rb +17 -7
  107. data/lib/evilution/runner/isolation_resolver.rb +23 -2
  108. data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +17 -10
  109. data/lib/evilution/source_ast_cache.rb +1 -1
  110. data/lib/evilution/spec_ast_cache.rb +1 -1
  111. data/lib/evilution/version.rb +1 -1
  112. data/lib/evilution.rb +23 -0
  113. metadata +27 -2
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../operator"
4
+
5
+ # Replace a lenient `to_i` with the strict `Integer()` conversion:
6
+ # `value.to_i` becomes `Integer(value)`, `value.to_i(16)` becomes
7
+ # `Integer(value, 16)`.
8
+ #
9
+ # `to_i` never fails — malformed input ("abc", "12abc", "") and nil become 0
10
+ # or a leading-digits prefix — where `Integer()` raises. `Integer()` also
11
+ # honours radix prefixes ("0x1A" is 26, "012" is octal 10) that `to_i`
12
+ # ignores. A survivor means no test feeds invalid or prefixed input.
13
+ #
14
+ # Numeric literal receivers are skipped: both conversions agree on them.
15
+ class Evilution::Mutator::Operator::ToIToInteger < Evilution::Mutator::Base
16
+ NUMERIC_LITERALS = [Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode].freeze
17
+ private_constant :NUMERIC_LITERALS
18
+
19
+ def visit_call_node(node)
20
+ rewrite(node) if node.name == :to_i && convertible?(node)
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def convertible?(node)
27
+ receiver = node.receiver
28
+ receiver && node.block.nil? && NUMERIC_LITERALS.none? { |type| receiver.is_a?(type) }
29
+ end
30
+
31
+ def rewrite(node)
32
+ arguments = node.arguments ? node.arguments.arguments : []
33
+ return if arguments.length > 1 || arguments.any?(Prism::SplatNode)
34
+
35
+ parts = [node.receiver, *arguments].map { |part| source_of(part) }
36
+ replace_span(node: node, target: node, replacement: "Integer(#{parts.join(", ")})")
37
+ end
38
+ end
@@ -49,4 +49,29 @@ module Evilution::Mutator::Primitives
49
49
  location = child.location
50
50
  byteslice_source(location.start_offset, location.length)
51
51
  end
52
+
53
+ # `operand`'s source, parenthesized unless it is a primary expression — so
54
+ # it can take a method call: `a + b` becomes `(a + b)` in `(a + b).eql?(c)`.
55
+ def receiver_source(operand)
56
+ primary_operand?(operand) ? source_of(operand) : "(#{source_of(operand)})"
57
+ end
58
+
59
+ PRIMARY_OPERANDS = [
60
+ Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode, Prism::ClassVariableReadNode,
61
+ Prism::GlobalVariableReadNode, Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::SelfNode,
62
+ Prism::ParenthesesNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::ArrayNode,
63
+ Prism::RegularExpressionNode
64
+ ].freeze
65
+ private_constant :PRIMARY_OPERANDS
66
+
67
+ METHOD_NAME = /\A[[:alpha:]_]/
68
+ private_constant :METHOD_NAME
69
+
70
+ # A named method call or an index (`a.size`, `a[0]`) binds tighter than a
71
+ # following `.`; an operator send (`a + b`, `!a`, `-a`) does not.
72
+ def primary_operand?(operand)
73
+ return true if PRIMARY_OPERANDS.any? { |type| operand.is_a?(type) }
74
+
75
+ operand.is_a?(Prism::CallNode) && (operand.name.match?(METHOD_NAME) || operand.name == :[])
76
+ end
52
77
  end
@@ -113,7 +113,30 @@ class Evilution::Mutator::Registry
113
113
  Evilution::Mutator::Operator::EqualityToIdentity,
114
114
  Evilution::Mutator::Operator::LambdaBody,
115
115
  Evilution::Mutator::Operator::BeginUnwrap,
116
- Evilution::Mutator::Operator::BlockParamRemoval
116
+ Evilution::Mutator::Operator::BlockParamRemoval,
117
+ Evilution::Mutator::Operator::CallToNil,
118
+ Evilution::Mutator::Operator::SafeNavigationRemoval,
119
+ Evilution::Mutator::Operator::AttributeWriteToRead,
120
+ Evilution::Mutator::Operator::ArgumentPropagation,
121
+ Evilution::Mutator::Operator::ArgumentListRemoval,
122
+ Evilution::Mutator::Operator::SymbolToProcReplacement,
123
+ Evilution::Mutator::Operator::DynamicDispatchResolution,
124
+ Evilution::Mutator::Operator::KeywordArgumentRemoval,
125
+ Evilution::Mutator::Operator::DoubleNegationRemoval,
126
+ Evilution::Mutator::Operator::RegexpAnchorToPredicate,
127
+ Evilution::Mutator::Operator::ReduceToSum,
128
+ Evilution::Mutator::Operator::ArrayCoercionToLiteral,
129
+ Evilution::Mutator::Operator::ToIToInteger,
130
+ Evilution::Mutator::Operator::CoercionEmptying,
131
+ Evilution::Mutator::Operator::ConstGetToConstantPath,
132
+ Evilution::Mutator::Operator::ProcToLambda,
133
+ Evilution::Mutator::Operator::DigToFetchChain,
134
+ Evilution::Mutator::Operator::InequalityToNegatedIdentity,
135
+ Evilution::Mutator::Operator::BinaryOperandPromotion,
136
+ Evilution::Mutator::Operator::ReceiverConstructorSwap,
137
+ Evilution::Mutator::Operator::BlockBodyToNil,
138
+ Evilution::Mutator::Operator::BlockBodyToRaise,
139
+ Evilution::Mutator::Operator::BlockBodyPromotion
117
140
  ].each { |op| registry.register(op) }
118
141
  registry
119
142
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../parallel"
3
4
  require_relative "work_queue"
4
5
 
5
6
  class Evilution::Parallel::Pool
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "erb"
4
4
  require "yaml"
5
- require_relative "../evilution"
5
+ require_relative "version"
6
6
 
7
7
  # EV-kdns / GH #817: nudge users running parallel jobs against SQLite to adopt
8
8
  # the parallel_tests convention. WorkQueue sets TEST_ENV_NUMBER per worker, but
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "diagnostic"
4
+
5
+ # Warms Rails state that initialises lazily on a process's first request, once
6
+ # in the parent after the preload, so forked mutations inherit it instead of
7
+ # each paying the cold start.
8
+ #
9
+ # Every step is side-effect free. A real request is deliberately NOT used:
10
+ # controller callbacks change process-global state -- I18n.locale,
11
+ # PaperTrail.request, a session row -- that every fork would then inherit,
12
+ # failing unrelated tests and reporting them as kills.
13
+ #
14
+ # A step whose library is not loaded is skipped silently; a step that raises
15
+ # (no application stylesheet, an unusual asset setup) is skipped with a note.
16
+ # Warm-up is an optimisation and must never fail or skew the run.
17
+ class Evilution::RailsWarmup
18
+ def initialize(application, stream: $stderr)
19
+ @application = application
20
+ @stream = stream
21
+ end
22
+
23
+ def call
24
+ step("i18n") { warm_i18n }
25
+ step("routes") { warm_routes }
26
+ step("assets") { warm_assets }
27
+ step("templates") { warm_templates }
28
+ end
29
+
30
+ private
31
+
32
+ def step(name)
33
+ yield
34
+ rescue StandardError, ScriptError => e
35
+ Evilution::Diagnostic.warn("[evilution] rails warmup: skipped #{name} (#{e.class}: #{e.message})", stream: @stream)
36
+ end
37
+
38
+ def warm_i18n
39
+ ::I18n.eager_load! if defined?(::I18n) && ::I18n.respond_to?(:eager_load!)
40
+ end
41
+
42
+ # Rails 8 can draw routes lazily; building url_helpers compiles the helpers
43
+ # module every request would otherwise build on first use.
44
+ def warm_routes
45
+ routes = @application.routes
46
+ routes.eager_load! if routes.respond_to?(:eager_load!)
47
+ routes.url_helpers
48
+ end
49
+
50
+ def warm_assets
51
+ ::ActionController::Base.helpers.stylesheet_path("application") if defined?(::ActionController::Base)
52
+ end
53
+
54
+ # Optional: jhawthorn/actionview_precompiler compiles templates up front.
55
+ def warm_templates
56
+ return unless Gem.loaded_specs.key?("actionview_precompiler")
57
+
58
+ require "actionview_precompiler"
59
+ ::ActionviewPrecompiler.precompile
60
+ end
61
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../html"
3
4
  require_relative "section"
4
5
  require_relative "stylesheet"
5
6
  require_relative "baseline_keys"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../result"
3
4
  require_relative "coverage_gap"
4
5
 
5
6
  class Evilution::Result::CoverageGapGrouper
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../result"
3
4
  require_relative "subject_score"
4
5
 
5
6
  # Groups mutation results by the subject they belong to and scores each one.
@@ -60,18 +60,15 @@ class Evilution::Runner::BaselineRunner
60
60
  return nil unless config.baseline? && subjects.any?
61
61
 
62
62
  log_start
63
- baseline = Evilution::Baseline.new(
64
- timeout: config.timeout,
65
- test_files: config.spec_files.empty? ? nil : config.spec_files,
66
- **integration_class.baseline_options
67
- )
68
- result = baseline.call(subjects)
63
+ result = build_baseline.call(subjects)
69
64
  log_complete(result)
70
65
  result
71
66
  end
72
67
 
68
+ # The same selector the baseline resolves with, so a surviving mutation is
69
+ # matched against exactly the spec files the baseline ran.
73
70
  def neutralization_resolver
74
- integration_class.baseline_options[:spec_resolver] || Evilution::SpecResolver.new
71
+ config.spec_selector
75
72
  end
76
73
 
77
74
  def neutralization_fallback_dir
@@ -82,6 +79,19 @@ class Evilution::Runner::BaselineRunner
82
79
 
83
80
  attr_reader :config, :hooks
84
81
 
82
+ # Resolves through config.spec_selector -- the object `run` uses, so
83
+ # spec_mappings / spec_pattern apply -- and skips unresolved sources unless
84
+ # the run falls back to the full suite.
85
+ def build_baseline
86
+ Evilution::Baseline.new(
87
+ timeout: config.timeout,
88
+ test_files: config.spec_files.empty? ? nil : config.spec_files,
89
+ spec_selector: config.spec_selector,
90
+ fallback_to_full_suite: config.fallback_to_full_suite?,
91
+ **integration_class.baseline_options
92
+ )
93
+ end
94
+
85
95
  def build_example_filter
86
96
  return nil unless config.example_targeting?
87
97
 
@@ -6,6 +6,7 @@ require_relative "../isolation/in_process"
6
6
  require_relative "../rails_detector"
7
7
  require_relative "../gem_detector"
8
8
  require_relative "../integration/loading/test_load_path"
9
+ require_relative "../rails_warmup"
9
10
 
10
11
  class Evilution::Runner::IsolationResolver
11
12
  PRELOAD_CANDIDATES = [
@@ -48,6 +49,7 @@ class Evilution::Runner::IsolationResolver
48
49
  prepare_load_path_for_preload(path)
49
50
  prepare_integration_for_preload
50
51
  require File.expand_path(path)
52
+ warm_up_rails
51
53
  rescue Evilution::ConfigError
52
54
  raise
53
55
  rescue ScriptError, StandardError => e
@@ -61,6 +63,21 @@ class Evilution::Runner::IsolationResolver
61
63
 
62
64
  attr_reader :config, :hooks
63
65
 
66
+ # Runs only after a preload (which is what loads Rails). This is primarily an
67
+ # optimisation for :fork isolation, where children inherit the warmed state.
68
+ def warm_up_rails
69
+ return unless config.warmup == :rails
70
+
71
+ application = ::Rails.application if defined?(::Rails) && ::Rails.respond_to?(:application)
72
+ unless application
73
+ warn_message = "[evilution] warmup: rails requested but Rails is not loaded (or has no application) after preload; skipping"
74
+ Evilution::Diagnostic.warn(warn_message)
75
+ return
76
+ end
77
+
78
+ Evilution::RailsWarmup.new(application).call
79
+ end
80
+
64
81
  # Under :fork, allow preloading — caller resolves whether a path exists (an
65
82
  # explicit --preload / preload: value, or an auto-detected rails_helper) and
66
83
  # bails early when none does. Under :in_process, only allow preloading when
@@ -131,7 +148,11 @@ class Evilution::Runner::IsolationResolver
131
148
  # have both (a bare `require "support/foo"` must still resolve from spec/).
132
149
  # - minitest/test-unit: a test/test_helper.rb doing a non-relative
133
150
  # `require "support/..."` needs test/ on $LOAD_PATH. Route through
134
- # TestLoadPath -- the same policy the per-mutation test load uses -- so preload and mutation paths agree.
151
+ # TestLoadPath, the policy the per-mutation test load uses, with one
152
+ # deliberate difference: outside_root_dirs: false. A resolved test file
153
+ # outside test/ and spec/ keeps its own directory, but a preload there
154
+ # (config/evilution_preload.rb) must not, or config/ lands ahead of the
155
+ # gems and config/puma.rb shadows the puma gem (GH #1597).
135
156
  def prepare_load_path_for_preload(preload_path)
136
157
  if config.integration == :rspec
137
158
  prepare_rspec_preload_load_path
@@ -148,7 +169,7 @@ class Evilution::Runner::IsolationResolver
148
169
 
149
170
  def prepare_test_preload_load_path(preload_path)
150
171
  base = detected_rails_root || Evilution.project_base_dir
151
- Evilution::Integration::Loading::TestLoadPath.add!([preload_path], base: base)
172
+ Evilution::Integration::Loading::TestLoadPath.add!([preload_path], base: base, outside_root_dirs: false)
152
173
  end
153
174
 
154
175
  def resolve_spec_dir
@@ -14,23 +14,30 @@ class Evilution::Runner::MutationExecutor::Neutralizer::BaselineFailed
14
14
  def call(result, baseline_result:)
15
15
  return result unless result.survived? && baseline_result && baseline_result.failed?
16
16
 
17
- if @config.spec_files.any?
18
- should_neutralize = true
19
- spec_file = nil
20
- else
21
- spec_file = @spec_resolver.call(result.mutation.file_path) || @fallback_dir
22
- should_neutralize = baseline_result.failed_spec_files.include?(spec_file)
23
- end
24
- return result unless should_neutralize
17
+ return neutralize(result, nil) if @config.spec_files.any?
18
+
19
+ failed = baseline_result.failed_spec_files
20
+ spec_file = covering_specs(result.mutation.file_path).find { |spec| failed.include?(spec) }
21
+ return result unless spec_file
25
22
 
26
23
  neutralize(result, spec_file)
27
24
  end
28
25
 
29
26
  private
30
27
 
28
+ # The spec files the baseline ran for this source: every file the resolver
29
+ # returns (a spec_selector may map one source to several), or the fallback
30
+ # directory when nothing resolves -- but only if the run falls back to the
31
+ # full suite; otherwise the baseline never ran it.
32
+ def covering_specs(file_path)
33
+ specs = Array(@spec_resolver.call(file_path))
34
+ return specs unless specs.empty?
35
+
36
+ @config.fallback_to_full_suite? ? [@fallback_dir] : []
37
+ end
38
+
31
39
  # The spec that was already red is the fact a reader needs: it says what to
32
- # fix before the run can say anything about these mutations
33
- # (EV-5pob / GH #1606).
40
+ # fix before the run can say anything about these mutations.
34
41
  def neutralize(result, spec_file)
35
42
  Evilution::Result::MutationResult.new(
36
43
  mutation: result.mutation,
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "digest"
4
4
  require "prism"
5
- require_relative "../evilution"
5
+ require_relative "version"
6
6
 
7
7
  # Content-hash-keyed LRU of Prism::ParseResult. Different source bytes always
8
8
  # yield a different key, so the cache is valid for the lifetime of the process.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "prism"
4
- require_relative "../evilution"
4
+ require_relative "version"
5
5
 
6
6
  class Evilution::SpecAstCache
7
7
  Block = Struct.new(:kind, :line, :end_line, :body_text)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/evilution.rb CHANGED
@@ -30,6 +30,29 @@ require_relative "evilution/mutator/operator/nil_replacement"
30
30
  require_relative "evilution/mutator/operator/boolean_operator_replacement"
31
31
  require_relative "evilution/mutator/operator/boolean_operand_promotion"
32
32
  require_relative "evilution/mutator/operator/boolean_expression_to_nil"
33
+ require_relative "evilution/mutator/operator/call_to_nil"
34
+ require_relative "evilution/mutator/operator/safe_navigation_removal"
35
+ require_relative "evilution/mutator/operator/attribute_write_to_read"
36
+ require_relative "evilution/mutator/operator/argument_propagation"
37
+ require_relative "evilution/mutator/operator/argument_list_removal"
38
+ require_relative "evilution/mutator/operator/symbol_to_proc_replacement"
39
+ require_relative "evilution/mutator/operator/dynamic_dispatch_resolution"
40
+ require_relative "evilution/mutator/operator/keyword_argument_removal"
41
+ require_relative "evilution/mutator/operator/double_negation_removal"
42
+ require_relative "evilution/mutator/operator/regexp_anchor_to_predicate"
43
+ require_relative "evilution/mutator/operator/reduce_to_sum"
44
+ require_relative "evilution/mutator/operator/array_coercion_to_literal"
45
+ require_relative "evilution/mutator/operator/to_i_to_integer"
46
+ require_relative "evilution/mutator/operator/coercion_emptying"
47
+ require_relative "evilution/mutator/operator/const_get_to_constant_path"
48
+ require_relative "evilution/mutator/operator/proc_to_lambda"
49
+ require_relative "evilution/mutator/operator/dig_to_fetch_chain"
50
+ require_relative "evilution/mutator/operator/inequality_to_negated_identity"
51
+ require_relative "evilution/mutator/operator/binary_operand_promotion"
52
+ require_relative "evilution/mutator/operator/receiver_constructor_swap"
53
+ require_relative "evilution/mutator/operator/block_body_to_nil"
54
+ require_relative "evilution/mutator/operator/block_body_to_raise"
55
+ require_relative "evilution/mutator/operator/block_body_promotion"
33
56
  require_relative "evilution/mutator/operator/arithmetic_replacement"
34
57
  require_relative "evilution/mutator/operator/string_literal"
35
58
  require_relative "evilution/mutator/operator/array_literal"
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: 1.2.0
4
+ version: 1.3.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-09-21 00:00:00.000000000 Z
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -201,6 +201,7 @@ files:
201
201
  - lib/evilution/config/validators/profile.rb
202
202
  - lib/evilution/config/validators/spec_mappings.rb
203
203
  - lib/evilution/config/validators/spec_pattern.rb
204
+ - lib/evilution/config/validators/warmup.rb
204
205
  - lib/evilution/coverage.rb
205
206
  - lib/evilution/coverage/digest.rb
206
207
  - lib/evilution/coverage/map.rb
@@ -306,15 +307,23 @@ files:
306
307
  - lib/evilution/mutator.rb
307
308
  - lib/evilution/mutator/base.rb
308
309
  - lib/evilution/mutator/operator.rb
310
+ - lib/evilution/mutator/operator/argument_list_removal.rb
309
311
  - lib/evilution/mutator/operator/argument_method_call_replacement.rb
310
312
  - lib/evilution/mutator/operator/argument_nil_substitution.rb
313
+ - lib/evilution/mutator/operator/argument_propagation.rb
311
314
  - lib/evilution/mutator/operator/argument_removal.rb
312
315
  - lib/evilution/mutator/operator/arithmetic_replacement.rb
316
+ - lib/evilution/mutator/operator/array_coercion_to_literal.rb
313
317
  - lib/evilution/mutator/operator/array_literal.rb
318
+ - lib/evilution/mutator/operator/attribute_write_to_read.rb
314
319
  - lib/evilution/mutator/operator/bang_method.rb
315
320
  - lib/evilution/mutator/operator/begin_unwrap.rb
321
+ - lib/evilution/mutator/operator/binary_operand_promotion.rb
316
322
  - lib/evilution/mutator/operator/bitwise_complement.rb
317
323
  - lib/evilution/mutator/operator/bitwise_replacement.rb
324
+ - lib/evilution/mutator/operator/block_body_promotion.rb
325
+ - lib/evilution/mutator/operator/block_body_to_nil.rb
326
+ - lib/evilution/mutator/operator/block_body_to_raise.rb
318
327
  - lib/evilution/mutator/operator/block_destructuring_expansion.rb
319
328
  - lib/evilution/mutator/operator/block_param_removal.rb
320
329
  - lib/evilution/mutator/operator/block_parameter_drop.rb
@@ -325,9 +334,11 @@ files:
325
334
  - lib/evilution/mutator/operator/boolean_operand_promotion.rb
326
335
  - lib/evilution/mutator/operator/boolean_operator_replacement.rb
327
336
  - lib/evilution/mutator/operator/break_statement.rb
337
+ - lib/evilution/mutator/operator/call_to_nil.rb
328
338
  - lib/evilution/mutator/operator/case_in.rb
329
339
  - lib/evilution/mutator/operator/case_when.rb
330
340
  - lib/evilution/mutator/operator/class_variable_write.rb
341
+ - lib/evilution/mutator/operator/coercion_emptying.rb
331
342
  - lib/evilution/mutator/operator/collection_replacement.rb
332
343
  - lib/evilution/mutator/operator/collection_return.rb
333
344
  - lib/evilution/mutator/operator/comparison_replacement.rb
@@ -335,7 +346,11 @@ files:
335
346
  - lib/evilution/mutator/operator/conditional_branch.rb
336
347
  - lib/evilution/mutator/operator/conditional_flip.rb
337
348
  - lib/evilution/mutator/operator/conditional_negation.rb
349
+ - lib/evilution/mutator/operator/const_get_to_constant_path.rb
338
350
  - lib/evilution/mutator/operator/defined_check.rb
351
+ - lib/evilution/mutator/operator/dig_to_fetch_chain.rb
352
+ - lib/evilution/mutator/operator/double_negation_removal.rb
353
+ - lib/evilution/mutator/operator/dynamic_dispatch_resolution.rb
339
354
  - lib/evilution/mutator/operator/ensure_removal.rb
340
355
  - lib/evilution/mutator/operator/equality_to_identity.rb
341
356
  - lib/evilution/mutator/operator/explicit_super_mutation.rb
@@ -348,10 +363,12 @@ files:
348
363
  - lib/evilution/mutator/operator/index_to_at.rb
349
364
  - lib/evilution/mutator/operator/index_to_dig.rb
350
365
  - lib/evilution/mutator/operator/index_to_fetch.rb
366
+ - lib/evilution/mutator/operator/inequality_to_negated_identity.rb
351
367
  - lib/evilution/mutator/operator/inline_rescue.rb
352
368
  - lib/evilution/mutator/operator/instance_variable_write.rb
353
369
  - lib/evilution/mutator/operator/integer_literal.rb
354
370
  - lib/evilution/mutator/operator/keyword_argument.rb
371
+ - lib/evilution/mutator/operator/keyword_argument_removal.rb
355
372
  - lib/evilution/mutator/operator/lambda_body.rb
356
373
  - lib/evilution/mutator/operator/last_expression_removal.rb
357
374
  - lib/evilution/mutator/operator/local_variable_assignment.rb
@@ -374,16 +391,21 @@ files:
374
391
  - lib/evilution/mutator/operator/pattern_predicate.rb
375
392
  - lib/evilution/mutator/operator/predicate_replacement.rb
376
393
  - lib/evilution/mutator/operator/predicate_to_nil.rb
394
+ - lib/evilution/mutator/operator/proc_to_lambda.rb
377
395
  - lib/evilution/mutator/operator/range_replacement.rb
396
+ - lib/evilution/mutator/operator/receiver_constructor_swap.rb
378
397
  - lib/evilution/mutator/operator/receiver_replacement.rb
379
398
  - lib/evilution/mutator/operator/redo_statement.rb
399
+ - lib/evilution/mutator/operator/reduce_to_sum.rb
380
400
  - lib/evilution/mutator/operator/regex_capture.rb
381
401
  - lib/evilution/mutator/operator/regex_simplification.rb
402
+ - lib/evilution/mutator/operator/regexp_anchor_to_predicate.rb
382
403
  - lib/evilution/mutator/operator/regexp_mutation.rb
383
404
  - lib/evilution/mutator/operator/rescue_body_replacement.rb
384
405
  - lib/evilution/mutator/operator/rescue_removal.rb
385
406
  - lib/evilution/mutator/operator/retry_removal.rb
386
407
  - lib/evilution/mutator/operator/return_value_removal.rb
408
+ - lib/evilution/mutator/operator/safe_navigation_removal.rb
387
409
  - lib/evilution/mutator/operator/scalar_return.rb
388
410
  - lib/evilution/mutator/operator/send_mutation.rb
389
411
  - lib/evilution/mutator/operator/splat_operator.rb
@@ -392,6 +414,8 @@ files:
392
414
  - lib/evilution/mutator/operator/string_literal.rb
393
415
  - lib/evilution/mutator/operator/superclass_removal.rb
394
416
  - lib/evilution/mutator/operator/symbol_literal.rb
417
+ - lib/evilution/mutator/operator/symbol_to_proc_replacement.rb
418
+ - lib/evilution/mutator/operator/to_i_to_integer.rb
395
419
  - lib/evilution/mutator/operator/typed_default_return.rb
396
420
  - lib/evilution/mutator/operator/yield_statement.rb
397
421
  - lib/evilution/mutator/operator/zsuper_removal.rb
@@ -416,6 +440,7 @@ files:
416
440
  - lib/evilution/process_cleanup.rb
417
441
  - lib/evilution/process_supervisor.rb
418
442
  - lib/evilution/rails_detector.rb
443
+ - lib/evilution/rails_warmup.rb
419
444
  - lib/evilution/related_spec_heuristic.rb
420
445
  - lib/evilution/reporter.rb
421
446
  - lib/evilution/reporter/cli.rb