evilution 0.27.0 → 0.29.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.
- checksums.yaml +4 -4
- data/.beads/interactions.jsonl +65 -0
- data/.rubocop_todo.yml +0 -1
- data/CHANGELOG.md +39 -0
- data/README.md +19 -0
- data/lib/evilution/ast/constant_names.rb +28 -11
- data/lib/evilution/ast/pattern/parser.rb +29 -17
- data/lib/evilution/baseline.rb +5 -4
- data/lib/evilution/cli/commands/session_diff.rb +6 -4
- data/lib/evilution/cli/commands/subjects.rb +6 -3
- data/lib/evilution/cli/commands/util_mutation.rb +24 -19
- data/lib/evilution/cli/parser/command_extractor.rb +9 -11
- data/lib/evilution/cli/parser/file_args.rb +3 -1
- data/lib/evilution/cli/parser/options_builder.rb +36 -1
- data/lib/evilution/cli/parser/stdin_reader.rb +2 -2
- data/lib/evilution/cli/parser.rb +18 -20
- data/lib/evilution/cli/printers/environment.rb +19 -19
- data/lib/evilution/cli/printers/session_diff.rb +8 -8
- data/lib/evilution/compare/diff_extractor/evilution.rb +22 -0
- data/lib/evilution/compare/diff_extractor/mutant.rb +30 -0
- data/lib/evilution/compare/diff_extractor.rb +6 -0
- data/lib/evilution/compare/fingerprint.rb +15 -72
- data/lib/evilution/compare/line_normalizer.rb +72 -0
- data/lib/evilution/compare/normalizer.rb +27 -9
- data/lib/evilution/config/validators/profile.rb +11 -0
- data/lib/evilution/config.rb +49 -32
- data/lib/evilution/disable_comment.rb +21 -12
- data/lib/evilution/integration/crash_detector.rb +2 -2
- data/lib/evilution/integration/loading/mutation_applier.rb +17 -12
- data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
- data/lib/evilution/integration/minitest.rb +25 -16
- data/lib/evilution/integration/minitest_crash_detector.rb +2 -2
- data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +11 -3
- data/lib/evilution/integration/rspec.rb +4 -0
- data/lib/evilution/isolation/fork.rb +43 -28
- data/lib/evilution/isolation/in_process.rb +10 -6
- data/lib/evilution/mcp/info_tool/actions/subjects.rb +32 -23
- data/lib/evilution/mcp/info_tool/actions/tests.rb +22 -12
- data/lib/evilution/mcp/info_tool/request_parser.rb +3 -1
- data/lib/evilution/mcp/info_tool.rb +7 -3
- data/lib/evilution/mcp/mutate_tool/option_parser.rb +3 -1
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
- data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +19 -9
- data/lib/evilution/mcp/mutate_tool.rb +27 -14
- data/lib/evilution/mcp/session_tool.rb +27 -20
- data/lib/evilution/mutation.rb +60 -42
- data/lib/evilution/mutator/base.rb +23 -21
- data/lib/evilution/mutator/operator/argument_nil_substitution.rb +11 -14
- data/lib/evilution/mutator/operator/argument_removal.rb +11 -14
- data/lib/evilution/mutator/operator/begin_unwrap.rb +17 -5
- data/lib/evilution/mutator/operator/bitwise_complement.rb +26 -19
- data/lib/evilution/mutator/operator/block_param_removal.rb +18 -8
- data/lib/evilution/mutator/operator/block_pass_removal.rb +19 -15
- data/lib/evilution/mutator/operator/case_when.rb +7 -5
- data/lib/evilution/mutator/operator/conditional_branch.rb +22 -22
- data/lib/evilution/mutator/operator/equality_to_identity.rb +8 -3
- data/lib/evilution/mutator/operator/explicit_super_mutation.rb +17 -13
- data/lib/evilution/mutator/operator/index_to_at.rb +5 -4
- data/lib/evilution/mutator/operator/index_to_dig.rb +12 -6
- data/lib/evilution/mutator/operator/index_to_fetch.rb +5 -4
- data/lib/evilution/mutator/operator/keyword_argument.rb +30 -25
- data/lib/evilution/mutator/operator/mixin_removal.rb +20 -14
- data/lib/evilution/mutator/operator/multiple_assignment.rb +12 -13
- data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
- data/lib/evilution/mutator/operator/receiver_replacement.rb +9 -6
- data/lib/evilution/mutator/operator/regex_simplification.rb +62 -67
- data/lib/evilution/mutator/operator/rescue_body_replacement.rb +9 -8
- data/lib/evilution/mutator/operator/rescue_removal.rb +4 -7
- data/lib/evilution/mutator/operator/superclass_removal.rb +21 -15
- data/lib/evilution/mutator/registry.rb +20 -0
- data/lib/evilution/parallel/work_queue/channel/frame.rb +5 -1
- data/lib/evilution/parallel/work_queue/dispatcher.rb +15 -8
- data/lib/evilution/parallel/work_queue/worker/loop.rb +1 -1
- data/lib/evilution/parallel/work_queue/worker.rb +10 -7
- data/lib/evilution/parallel/work_queue.rb +35 -18
- data/lib/evilution/process_cleanup.rb +19 -0
- data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +13 -8
- data/lib/evilution/reporter/cli/line_formatters/mutations.rb +17 -8
- data/lib/evilution/reporter/html/baseline_keys.rb +1 -1
- data/lib/evilution/reporter/html/diff_formatter.rb +1 -1
- data/lib/evilution/reporter/html/escape.rb +1 -1
- data/lib/evilution/reporter/html/section.rb +1 -1
- data/lib/evilution/reporter/html/sections.rb +4 -2
- data/lib/evilution/reporter/html/stylesheet.rb +1 -1
- data/lib/evilution/reporter/html.rb +8 -3
- data/lib/evilution/reporter/json.rb +52 -18
- data/lib/evilution/reporter/suggestion/diff_helpers.rb +0 -13
- data/lib/evilution/reporter/suggestion/diff_lines.rb +28 -0
- data/lib/evilution/reporter/suggestion/registry.rb +1 -5
- data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
- data/lib/evilution/reporter/suggestion/templates/minitest.rb +361 -649
- data/lib/evilution/reporter/suggestion/templates/rspec.rb +362 -603
- data/lib/evilution/reporter/suggestion/templates.rb +6 -0
- data/lib/evilution/result/error_info.rb +20 -0
- data/lib/evilution/result/memory_stats.rb +20 -0
- data/lib/evilution/result/mutation_result.rb +30 -14
- data/lib/evilution/runner/baseline_runner.rb +16 -10
- data/lib/evilution/runner/diagnostics.rb +14 -11
- data/lib/evilution/runner/isolation_resolver.rb +12 -11
- data/lib/evilution/runner/mutation_executor/mutation_runner.rb +1 -3
- data/lib/evilution/runner/mutation_executor/neutralization_pipeline.rb +1 -2
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +3 -10
- data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +3 -10
- data/lib/evilution/runner/mutation_executor/neutralizer.rb +11 -0
- data/lib/evilution/runner/mutation_executor/result_cache.rb +4 -4
- data/lib/evilution/runner/mutation_executor/result_notifier.rb +1 -3
- data/lib/evilution/runner/mutation_executor/result_packer.rb +11 -9
- data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +33 -13
- data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +2 -4
- data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
- data/lib/evilution/runner/mutation_executor.rb +14 -20
- data/lib/evilution/runner/mutation_planner.rb +38 -19
- data/lib/evilution/runner/report_publisher.rb +1 -2
- data/lib/evilution/runner/subject_pipeline.rb +22 -13
- data/lib/evilution/runner.rb +36 -34
- data/lib/evilution/session/diff.rb +15 -6
- data/lib/evilution/spec_ast_cache.rb +26 -12
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +1 -0
- data/script/memory_check +14 -6
- data/scripts/benchmark_density +10 -9
- data/scripts/compare_mutations +38 -21
- data/scripts/mutant_json_adapter +7 -4
- metadata +15 -3
- data/lib/evilution/reporter/html/namespace.rb +0 -11
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../result"
|
|
4
|
+
|
|
5
|
+
class Evilution::Result::ErrorInfo
|
|
6
|
+
attr_reader :message, :klass, :backtrace
|
|
7
|
+
|
|
8
|
+
def self.from_fields(message: nil, klass: nil, backtrace: nil)
|
|
9
|
+
return nil if message.nil? && klass.nil? && backtrace.nil?
|
|
10
|
+
|
|
11
|
+
new(message: message, klass: klass, backtrace: backtrace)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(message: nil, klass: nil, backtrace: nil)
|
|
15
|
+
@message = message
|
|
16
|
+
@klass = klass
|
|
17
|
+
@backtrace = backtrace.nil? ? nil : backtrace.dup.freeze
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../result"
|
|
4
|
+
|
|
5
|
+
class Evilution::Result::MemoryStats
|
|
6
|
+
attr_reader :child_rss_kb, :memory_delta_kb, :parent_rss_kb
|
|
7
|
+
|
|
8
|
+
def self.from_fields(child_rss_kb: nil, memory_delta_kb: nil, parent_rss_kb: nil)
|
|
9
|
+
return nil if child_rss_kb.nil? && memory_delta_kb.nil? && parent_rss_kb.nil?
|
|
10
|
+
|
|
11
|
+
new(child_rss_kb: child_rss_kb, memory_delta_kb: memory_delta_kb, parent_rss_kb: parent_rss_kb)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(child_rss_kb: nil, memory_delta_kb: nil, parent_rss_kb: nil)
|
|
15
|
+
@child_rss_kb = child_rss_kb
|
|
16
|
+
@memory_delta_kb = memory_delta_kb
|
|
17
|
+
@parent_rss_kb = parent_rss_kb
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../result"
|
|
4
|
+
require_relative "error_info"
|
|
5
|
+
require_relative "memory_stats"
|
|
4
6
|
|
|
5
7
|
class Evilution::Result::MutationResult
|
|
6
8
|
STATUSES = %i[killed survived timeout error neutral equivalent unresolved unparseable].freeze
|
|
7
9
|
|
|
8
|
-
attr_reader :mutation, :status, :duration, :killing_test, :test_command,
|
|
9
|
-
:child_rss_kb, :memory_delta_kb, :parent_rss_kb,
|
|
10
|
-
:error_message, :error_class, :error_backtrace
|
|
10
|
+
attr_reader :mutation, :status, :duration, :killing_test, :test_command, :memory, :error
|
|
11
11
|
|
|
12
|
-
# rubocop:disable Metrics/ParameterLists
|
|
13
12
|
def initialize(mutation:, status:, duration: 0.0, killing_test: nil,
|
|
14
|
-
test_command: nil,
|
|
15
|
-
parent_rss_kb: nil, error_message: nil, error_class: nil,
|
|
16
|
-
error_backtrace: nil)
|
|
17
|
-
# rubocop:enable Metrics/ParameterLists
|
|
13
|
+
test_command: nil, memory: nil, error: nil)
|
|
18
14
|
raise ArgumentError, "invalid status: #{status}" unless STATUSES.include?(status)
|
|
19
15
|
|
|
20
16
|
@mutation = mutation
|
|
@@ -22,15 +18,35 @@ class Evilution::Result::MutationResult
|
|
|
22
18
|
@duration = duration
|
|
23
19
|
@killing_test = killing_test
|
|
24
20
|
@test_command = test_command
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
@parent_rss_kb = parent_rss_kb
|
|
28
|
-
@error_message = error_message
|
|
29
|
-
@error_class = error_class
|
|
30
|
-
@error_backtrace = error_backtrace.nil? ? nil : error_backtrace.dup.freeze
|
|
21
|
+
@memory = memory
|
|
22
|
+
@error = error
|
|
31
23
|
freeze
|
|
32
24
|
end
|
|
33
25
|
|
|
26
|
+
def child_rss_kb
|
|
27
|
+
@memory.nil? ? nil : @memory.child_rss_kb
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def memory_delta_kb
|
|
31
|
+
@memory.nil? ? nil : @memory.memory_delta_kb
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parent_rss_kb
|
|
35
|
+
@memory.nil? ? nil : @memory.parent_rss_kb
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def error_message
|
|
39
|
+
@error.nil? ? nil : @error.message
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def error_class
|
|
43
|
+
@error.nil? ? nil : @error.klass
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def error_backtrace
|
|
47
|
+
@error.nil? ? nil : @error.backtrace
|
|
48
|
+
end
|
|
49
|
+
|
|
34
50
|
def killed?
|
|
35
51
|
status == :killed
|
|
36
52
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../runner"
|
|
3
4
|
require_relative "../baseline"
|
|
4
5
|
require_relative "../spec_resolver"
|
|
5
6
|
require_relative "../integration/rspec"
|
|
@@ -8,8 +9,6 @@ require_relative "../example_filter"
|
|
|
8
9
|
require_relative "../spec_ast_cache"
|
|
9
10
|
require_relative "../source_ast_cache"
|
|
10
11
|
|
|
11
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
12
|
-
|
|
13
12
|
unless defined?(Evilution::Runner::INTEGRATIONS)
|
|
14
13
|
Evilution::Runner::INTEGRATIONS = {
|
|
15
14
|
rspec: Evilution::Integration::RSpec,
|
|
@@ -31,18 +30,25 @@ class Evilution::Runner::BaselineRunner
|
|
|
31
30
|
|
|
32
31
|
def build_integration
|
|
33
32
|
klass = integration_class
|
|
34
|
-
|
|
35
|
-
kwargs
|
|
36
|
-
|
|
33
|
+
kwargs = base_integration_kwargs
|
|
34
|
+
kwargs.merge!(rspec_integration_kwargs) if klass == Evilution::Integration::RSpec
|
|
35
|
+
klass.new(**kwargs)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def base_integration_kwargs
|
|
39
|
+
{
|
|
40
|
+
test_files: config.spec_files.empty? ? nil : config.spec_files,
|
|
37
41
|
hooks: hooks,
|
|
38
42
|
fallback_to_full_suite: config.fallback_to_full_suite?,
|
|
39
43
|
spec_selector: config.spec_selector
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def rspec_integration_kwargs
|
|
48
|
+
{
|
|
49
|
+
related_specs_heuristic: config.related_specs_heuristic?,
|
|
50
|
+
example_filter: build_example_filter
|
|
51
|
+
}
|
|
46
52
|
end
|
|
47
53
|
|
|
48
54
|
def call(subjects)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../runner"
|
|
3
4
|
require_relative "../memory"
|
|
4
5
|
require_relative "../parallel/pool"
|
|
5
6
|
|
|
6
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
|
|
8
7
|
class Evilution::Runner::Diagnostics
|
|
9
8
|
def initialize(config, stderr: $stderr)
|
|
10
9
|
@config = config
|
|
@@ -33,19 +32,23 @@ class Evilution::Runner::Diagnostics
|
|
|
33
32
|
def log_mutation_diagnostics(result)
|
|
34
33
|
return unless verbose?
|
|
35
34
|
|
|
36
|
-
parts =
|
|
37
|
-
|
|
35
|
+
parts = mutation_metric_parts(result)
|
|
36
|
+
stderr.write("[verbose] #{result.mutation}: #{parts.join(", ")}\n") unless parts.empty?
|
|
38
37
|
|
|
39
|
-
if result.
|
|
40
|
-
|
|
41
|
-
parts << format("delta: %<sign>s%<mb>.1f MB", sign: sign, mb: result.memory_delta_kb / 1024.0)
|
|
42
|
-
end
|
|
38
|
+
log_mutation_error(result) if result.error?
|
|
39
|
+
end
|
|
43
40
|
|
|
41
|
+
def mutation_metric_parts(result)
|
|
42
|
+
parts = []
|
|
43
|
+
parts << format("child_rss: %<mb>.1f MB", mb: result.child_rss_kb / 1024.0) if result.child_rss_kb
|
|
44
|
+
parts << format_memory_delta(result.memory_delta_kb) if result.memory_delta_kb
|
|
44
45
|
parts << gc_stats_string
|
|
46
|
+
parts
|
|
47
|
+
end
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
def format_memory_delta(delta_kb)
|
|
50
|
+
sign = delta_kb.negative? ? "" : "+"
|
|
51
|
+
format("delta: %<sign>s%<mb>.1f MB", sign: sign, mb: delta_kb / 1024.0)
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
def log_worker_stats(stats)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../runner"
|
|
3
4
|
require_relative "../isolation/fork"
|
|
4
5
|
require_relative "../isolation/in_process"
|
|
5
6
|
require_relative "../rails_detector"
|
|
6
7
|
|
|
7
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
8
|
-
|
|
9
8
|
class Evilution::Runner::IsolationResolver
|
|
10
9
|
PRELOAD_CANDIDATES = [
|
|
11
10
|
File.join("spec", "rails_helper.rb"),
|
|
@@ -109,16 +108,18 @@ class Evilution::Runner::IsolationResolver
|
|
|
109
108
|
end
|
|
110
109
|
|
|
111
110
|
def resolve_preload_path
|
|
112
|
-
if config.preload.is_a?(String)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
return resolve_explicit_preload(config.preload) if config.preload.is_a?(String)
|
|
112
|
+
|
|
113
|
+
resolve_autodetected_preload
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def resolve_explicit_preload(path)
|
|
117
|
+
return path if File.file?(path)
|
|
118
|
+
|
|
119
|
+
raise Evilution::ConfigError.new("preload file not found: #{path.inspect}", file: path)
|
|
120
|
+
end
|
|
121
121
|
|
|
122
|
+
def resolve_autodetected_preload
|
|
122
123
|
root = detected_rails_root
|
|
123
124
|
return nil unless root
|
|
124
125
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../mutation_executor"
|
|
3
4
|
require_relative "../../result/mutation_result"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
|
|
8
6
|
class Evilution::Runner::MutationExecutor::MutationRunner
|
|
9
7
|
def initialize(config:, cache:, isolator:)
|
|
10
8
|
@config = config
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
3
|
+
require_relative "../mutation_executor"
|
|
5
4
|
|
|
6
5
|
class Evilution::Runner::MutationExecutor::NeutralizationPipeline
|
|
7
6
|
def initialize(neutralizers)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../neutralizer"
|
|
3
4
|
require_relative "../../../result/mutation_result"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
module Evilution::Runner::MutationExecutor::Neutralizer; end unless defined?(Evilution::Runner::MutationExecutor::Neutralizer)
|
|
8
|
-
|
|
9
6
|
class Evilution::Runner::MutationExecutor::Neutralizer::BaselineFailed
|
|
10
7
|
def initialize(config:, spec_resolver:, fallback_dir:)
|
|
11
8
|
@config = config
|
|
@@ -35,12 +32,8 @@ class Evilution::Runner::MutationExecutor::Neutralizer::BaselineFailed
|
|
|
35
32
|
status: :neutral,
|
|
36
33
|
duration: result.duration,
|
|
37
34
|
test_command: result.test_command,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
parent_rss_kb: result.parent_rss_kb,
|
|
41
|
-
error_message: result.error_message,
|
|
42
|
-
error_class: result.error_class,
|
|
43
|
-
error_backtrace: result.error_backtrace
|
|
35
|
+
memory: result.memory,
|
|
36
|
+
error: result.error
|
|
44
37
|
)
|
|
45
38
|
end
|
|
46
39
|
end
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../neutralizer"
|
|
3
4
|
require_relative "../../../result/mutation_result"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
module Evilution::Runner::MutationExecutor::Neutralizer; end unless defined?(Evilution::Runner::MutationExecutor::Neutralizer)
|
|
8
|
-
|
|
9
6
|
# Reclassify results as :neutral when the failure was caused by test
|
|
10
7
|
# infrastructure rather than by the mutation. Two independent paths:
|
|
11
8
|
#
|
|
@@ -64,12 +61,8 @@ class Evilution::Runner::MutationExecutor::Neutralizer::InfraError
|
|
|
64
61
|
status: :neutral,
|
|
65
62
|
duration: result.duration,
|
|
66
63
|
test_command: result.test_command,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
parent_rss_kb: result.parent_rss_kb,
|
|
70
|
-
error_message: result.error_message,
|
|
71
|
-
error_class: result.error_class,
|
|
72
|
-
error_backtrace: result.error_backtrace
|
|
64
|
+
memory: result.memory,
|
|
65
|
+
error: result.error
|
|
73
66
|
)
|
|
74
67
|
end
|
|
75
68
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../mutation_executor"
|
|
4
|
+
|
|
5
|
+
# Namespace for MutationExecutor's neutralization rules (InfraError,
|
|
6
|
+
# BaselineFailed). Concrete neutralizer classes live in neutralizer/*.rb and
|
|
7
|
+
# are autoloaded on first reference.
|
|
8
|
+
module Evilution::Runner::MutationExecutor::Neutralizer
|
|
9
|
+
autoload :InfraError, File.expand_path("neutralizer/infra_error", __dir__)
|
|
10
|
+
autoload :BaselineFailed, File.expand_path("neutralizer/baseline_failed", __dir__)
|
|
11
|
+
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../mutation_executor"
|
|
3
4
|
require_relative "../../result/mutation_result"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
|
|
8
6
|
class Evilution::Runner::MutationExecutor::ResultCache
|
|
9
7
|
CACHEABLE_STATUSES = %i[killed timeout].freeze
|
|
10
8
|
private_constant :CACHEABLE_STATUSES
|
|
11
9
|
|
|
10
|
+
Partition = Data.define(:uncached_indices, :cached_results)
|
|
11
|
+
|
|
12
12
|
def initialize(backend)
|
|
13
13
|
@backend = backend
|
|
14
14
|
end
|
|
@@ -58,7 +58,7 @@ class Evilution::Runner::MutationExecutor::ResultCache
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Partition.new(uncached_indices: uncached_indices, cached_results: cached_results)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
private
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../mutation_executor"
|
|
3
4
|
require_relative "../../reporter/progress_bar"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
|
|
8
6
|
class Evilution::Runner::MutationExecutor::ResultNotifier
|
|
9
7
|
def initialize(config, diagnostics:, on_result:)
|
|
10
8
|
@config = config
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../mutation_executor"
|
|
3
4
|
require_relative "../../result/mutation_result"
|
|
4
5
|
|
|
5
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
6
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
7
|
-
|
|
8
6
|
class Evilution::Runner::MutationExecutor::ResultPacker
|
|
9
7
|
def compact(result)
|
|
10
8
|
{
|
|
@@ -28,12 +26,16 @@ class Evilution::Runner::MutationExecutor::ResultPacker
|
|
|
28
26
|
duration: data[:duration],
|
|
29
27
|
killing_test: data[:killing_test],
|
|
30
28
|
test_command: data[:test_command],
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
memory: Evilution::Result::MemoryStats.from_fields(
|
|
30
|
+
child_rss_kb: data[:child_rss_kb],
|
|
31
|
+
memory_delta_kb: data[:memory_delta_kb],
|
|
32
|
+
parent_rss_kb: data[:parent_rss_kb]
|
|
33
|
+
),
|
|
34
|
+
error: Evilution::Result::ErrorInfo.from_fields(
|
|
35
|
+
message: data[:error_message],
|
|
36
|
+
klass: data[:error_class],
|
|
37
|
+
backtrace: data[:error_backtrace]
|
|
38
|
+
)
|
|
37
39
|
)
|
|
38
40
|
end
|
|
39
41
|
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
5
|
-
module Evilution::Runner::MutationExecutor::Strategy; end unless defined?(Evilution::Runner::MutationExecutor::Strategy)
|
|
3
|
+
require_relative "../strategy"
|
|
6
4
|
|
|
7
5
|
class Evilution::Runner::MutationExecutor::Strategy::Parallel
|
|
8
6
|
def initialize(cache:, isolator:, packer:, pipeline:, notifier:, pool_factory:, config:, diagnostics: nil)
|
|
@@ -20,8 +18,17 @@ class Evilution::Runner::MutationExecutor::Strategy::Parallel
|
|
|
20
18
|
@notifier.start(mutations.length)
|
|
21
19
|
pool = @pool_factory.call
|
|
22
20
|
state = { results: [], truncated: false, completed: 0 }
|
|
23
|
-
all_worker_stats =
|
|
21
|
+
all_worker_stats = run_batches(mutations, pool, baseline_result, integration, state)
|
|
24
22
|
|
|
23
|
+
log_worker_diagnostics(all_worker_stats)
|
|
24
|
+
@notifier.finish
|
|
25
|
+
build_result(state)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def run_batches(mutations, pool, baseline_result, integration, state)
|
|
31
|
+
all_worker_stats = []
|
|
25
32
|
mutations.each_slice(@config.jobs) do |batch|
|
|
26
33
|
break if state[:truncated]
|
|
27
34
|
|
|
@@ -29,24 +36,37 @@ class Evilution::Runner::MutationExecutor::Strategy::Parallel
|
|
|
29
36
|
all_worker_stats.concat(pool.worker_stats)
|
|
30
37
|
process_batch(batch_results, baseline_result, state)
|
|
31
38
|
end
|
|
39
|
+
all_worker_stats
|
|
40
|
+
end
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
|
|
42
|
+
def log_worker_diagnostics(all_worker_stats)
|
|
43
|
+
return unless @diagnostics
|
|
44
|
+
|
|
45
|
+
@diagnostics.log_worker_stats(@diagnostics.aggregate_worker_stats(all_worker_stats))
|
|
36
46
|
end
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
def build_result(state)
|
|
49
|
+
Evilution::Runner::MutationExecutor::ExecutionResult.new(results: state[:results], truncated: state[:truncated])
|
|
50
|
+
end
|
|
39
51
|
|
|
40
52
|
def run_batch(batch, pool, integration)
|
|
41
|
-
|
|
42
|
-
worker_results = run_uncached(batch, uncached_indices, pool, integration)
|
|
43
|
-
compact_results = merge(batch, uncached_indices, cached_results, worker_results)
|
|
44
|
-
batch_results = batch
|
|
45
|
-
|
|
53
|
+
partition = @cache.partition(batch, packer: @packer)
|
|
54
|
+
worker_results = run_uncached(batch, partition.uncached_indices, pool, integration)
|
|
55
|
+
compact_results = merge(batch, partition.uncached_indices, partition.cached_results, worker_results)
|
|
56
|
+
batch_results = rebuild_results(batch, compact_results)
|
|
57
|
+
cache_results(batch_results, partition.uncached_indices)
|
|
46
58
|
batch.each(&:strip_sources!)
|
|
47
59
|
batch_results
|
|
48
60
|
end
|
|
49
61
|
|
|
62
|
+
def rebuild_results(batch, compact_results)
|
|
63
|
+
batch.zip(compact_results).map { |m, h| @packer.rebuild(m, h) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def cache_results(batch_results, uncached_indices)
|
|
67
|
+
uncached_indices.each { |i| @cache.store(batch_results[i].mutation, batch_results[i]) }
|
|
68
|
+
end
|
|
69
|
+
|
|
50
70
|
def run_uncached(batch, uncached_indices, pool, integration)
|
|
51
71
|
return [] if uncached_indices.empty?
|
|
52
72
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class Evilution::Runner::MutationExecutor; end unless defined?(Evilution::Runner::MutationExecutor) # rubocop:disable Lint/EmptyClass
|
|
5
|
-
module Evilution::Runner::MutationExecutor::Strategy; end unless defined?(Evilution::Runner::MutationExecutor::Strategy)
|
|
3
|
+
require_relative "../strategy"
|
|
6
4
|
|
|
7
5
|
class Evilution::Runner::MutationExecutor::Strategy::Sequential
|
|
8
6
|
def initialize(runner:, pipeline:, notifier:)
|
|
@@ -29,6 +27,6 @@ class Evilution::Runner::MutationExecutor::Strategy::Sequential
|
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
@notifier.finish
|
|
32
|
-
|
|
30
|
+
Evilution::Runner::MutationExecutor::ExecutionResult.new(results: results, truncated: truncated)
|
|
33
31
|
end
|
|
34
32
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../mutation_executor"
|
|
4
|
+
|
|
5
|
+
# Namespace for MutationExecutor's execution strategies (Sequential, Parallel).
|
|
6
|
+
# Concrete strategy classes live in strategy/{sequential,parallel}.rb and are
|
|
7
|
+
# autoloaded on first reference.
|
|
8
|
+
module Evilution::Runner::MutationExecutor::Strategy
|
|
9
|
+
autoload :Sequential, File.expand_path("strategy/sequential", __dir__)
|
|
10
|
+
autoload :Parallel, File.expand_path("strategy/parallel", __dir__)
|
|
11
|
+
end
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
4
|
-
require_relative "mutation_executor/result_cache"
|
|
5
|
-
require_relative "mutation_executor/result_packer"
|
|
6
|
-
require_relative "mutation_executor/result_notifier"
|
|
7
|
-
require_relative "mutation_executor/mutation_runner"
|
|
8
|
-
require_relative "mutation_executor/neutralization_pipeline"
|
|
9
|
-
require_relative "mutation_executor/neutralizer/infra_error"
|
|
10
|
-
require_relative "mutation_executor/neutralizer/baseline_failed"
|
|
11
|
-
require_relative "mutation_executor/strategy/sequential"
|
|
12
|
-
require_relative "mutation_executor/strategy/parallel"
|
|
13
|
-
|
|
14
|
-
class Evilution::Runner; end unless defined?(Evilution::Runner) # rubocop:disable Lint/EmptyClass
|
|
3
|
+
require_relative "../runner"
|
|
15
4
|
|
|
16
5
|
class Evilution::Runner::MutationExecutor
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
ExecutionResult = Data.define(:results, :truncated)
|
|
7
|
+
|
|
8
|
+
autoload :ResultCache, File.expand_path("mutation_executor/result_cache", __dir__)
|
|
9
|
+
autoload :ResultPacker, File.expand_path("mutation_executor/result_packer", __dir__)
|
|
10
|
+
autoload :ResultNotifier, File.expand_path("mutation_executor/result_notifier", __dir__)
|
|
11
|
+
autoload :MutationRunner, File.expand_path("mutation_executor/mutation_runner", __dir__)
|
|
12
|
+
autoload :NeutralizationPipeline, File.expand_path("mutation_executor/neutralization_pipeline", __dir__)
|
|
13
|
+
autoload :Strategy, File.expand_path("mutation_executor/strategy", __dir__)
|
|
14
|
+
autoload :Neutralizer, File.expand_path("mutation_executor/neutralizer", __dir__)
|
|
21
15
|
|
|
22
16
|
def initialize(config, isolator:, baseline_runner:, cache:, hooks:, diagnostics:, on_result: nil)
|
|
23
17
|
@config = config
|
|
@@ -53,8 +47,8 @@ class Evilution::Runner::MutationExecutor
|
|
|
53
47
|
def build_pipeline(spec_resolver)
|
|
54
48
|
NeutralizationPipeline.new(
|
|
55
49
|
[
|
|
56
|
-
InfraError.new,
|
|
57
|
-
BaselineFailed.new(
|
|
50
|
+
Neutralizer::InfraError.new,
|
|
51
|
+
Neutralizer::BaselineFailed.new(
|
|
58
52
|
config: @config,
|
|
59
53
|
spec_resolver: spec_resolver || ->(_f) {},
|
|
60
54
|
fallback_dir: @baseline_runner.neutralization_fallback_dir
|
|
@@ -64,7 +58,7 @@ class Evilution::Runner::MutationExecutor
|
|
|
64
58
|
end
|
|
65
59
|
|
|
66
60
|
def build_sequential(notifier, pipeline)
|
|
67
|
-
Sequential.new(
|
|
61
|
+
Strategy::Sequential.new(
|
|
68
62
|
runner: MutationRunner.new(config: @config, cache: @cache, isolator: @isolator),
|
|
69
63
|
pipeline: pipeline,
|
|
70
64
|
notifier: notifier
|
|
@@ -72,7 +66,7 @@ class Evilution::Runner::MutationExecutor
|
|
|
72
66
|
end
|
|
73
67
|
|
|
74
68
|
def build_parallel(notifier, pipeline)
|
|
75
|
-
Parallel.new(
|
|
69
|
+
Strategy::Parallel.new(
|
|
76
70
|
cache: @cache,
|
|
77
71
|
isolator: @isolator,
|
|
78
72
|
packer: @packer,
|