evilution 0.27.0 → 0.28.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 +13 -0
- data/.rubocop_todo.yml +0 -1
- data/CHANGELOG.md +32 -0
- data/README.md +19 -0
- data/lib/evilution/baseline.rb +5 -4
- data/lib/evilution/cli/parser/options_builder.rb +7 -0
- 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 +17 -4
- data/lib/evilution/config/validators/profile.rb +11 -0
- data/lib/evilution/config.rb +40 -23
- data/lib/evilution/integration/crash_detector.rb +2 -2
- data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
- 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/isolation/fork.rb +16 -11
- data/lib/evilution/isolation/in_process.rb +10 -6
- data/lib/evilution/mcp/info_tool.rb +0 -2
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
- data/lib/evilution/mcp/session_tool.rb +0 -2
- data/lib/evilution/mutation.rb +47 -27
- data/lib/evilution/mutator/base.rb +8 -8
- data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
- 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/worker/loop.rb +1 -1
- data/lib/evilution/process_cleanup.rb +19 -0
- 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/suggestion/registry.rb +1 -5
- data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
- data/lib/evilution/reporter/suggestion/templates/minitest.rb +349 -643
- data/lib/evilution/reporter/suggestion/templates/rspec.rb +351 -598
- 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 +1 -2
- data/lib/evilution/runner/diagnostics.rb +1 -2
- data/lib/evilution/runner/isolation_resolver.rb +1 -2
- 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 +1 -3
- 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 +1 -3
- data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +1 -3
- data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
- data/lib/evilution/runner/mutation_executor.rb +12 -20
- data/lib/evilution/runner/mutation_planner.rb +1 -2
- data/lib/evilution/runner/report_publisher.rb +1 -2
- data/lib/evilution/runner/subject_pipeline.rb +1 -2
- data/lib/evilution/runner.rb +33 -31
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +1 -0
- data/script/memory_check +3 -1
- metadata +14 -3
- data/lib/evilution/reporter/html/namespace.rb +0 -11
|
@@ -9,7 +9,10 @@ class Evilution::Integration::RSpec::StateGuard::ObjectSpaceExampleGroups
|
|
|
9
9
|
groups = Set.new
|
|
10
10
|
ObjectSpace.each_object(Class) do |klass|
|
|
11
11
|
groups << klass.object_id if klass < ::RSpec::Core::ExampleGroup
|
|
12
|
-
rescue TypeError
|
|
12
|
+
rescue TypeError
|
|
13
|
+
# ObjectSpace iteration may surface partially-initialized or anonymous
|
|
14
|
+
# classes whose `<` comparison raises. Skipping them is safe — they
|
|
15
|
+
# cannot be ExampleGroup descendants we need to track.
|
|
13
16
|
end
|
|
14
17
|
groups
|
|
15
18
|
end
|
|
@@ -23,13 +26,18 @@ class Evilution::Integration::RSpec::StateGuard::ObjectSpaceExampleGroups
|
|
|
23
26
|
|
|
24
27
|
klass.constants(false).each do |const|
|
|
25
28
|
klass.send(:remove_const, const)
|
|
26
|
-
rescue NameError
|
|
29
|
+
rescue NameError
|
|
30
|
+
# Constant may have been removed concurrently (e.g. via autoload
|
|
31
|
+
# reload) between #constants(false) and #remove_const. Best-effort
|
|
32
|
+
# cleanup — nothing to do if it's already gone.
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
klass.instance_variables.each do |ivar|
|
|
30
36
|
klass.remove_instance_variable(ivar)
|
|
31
37
|
end
|
|
32
|
-
rescue TypeError
|
|
38
|
+
rescue TypeError
|
|
39
|
+
# Same defensive case as #snapshot: skip classes whose `<` raises
|
|
40
|
+
# mid-iteration.
|
|
33
41
|
end
|
|
34
42
|
end
|
|
35
43
|
end
|
|
@@ -5,6 +5,7 @@ require "tmpdir"
|
|
|
5
5
|
require_relative "../memory"
|
|
6
6
|
require_relative "../temp_dir_tracker"
|
|
7
7
|
require_relative "../child_output"
|
|
8
|
+
require_relative "../process_cleanup"
|
|
8
9
|
|
|
9
10
|
require_relative "../isolation"
|
|
10
11
|
|
|
@@ -49,13 +50,13 @@ class Evilution::Isolation::Fork
|
|
|
49
50
|
read_io&.close
|
|
50
51
|
write_io&.close
|
|
51
52
|
ensure_reaped(pid)
|
|
52
|
-
restore_original_source
|
|
53
|
+
restore_original_source
|
|
53
54
|
FileUtils.rm_rf(sandbox_dir) if sandbox_dir
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
private
|
|
57
58
|
|
|
58
|
-
def restore_original_source
|
|
59
|
+
def restore_original_source
|
|
59
60
|
Evilution::TempDirTracker.cleanup_all
|
|
60
61
|
end
|
|
61
62
|
|
|
@@ -88,7 +89,7 @@ class Evilution::Isolation::Fork
|
|
|
88
89
|
if data.empty?
|
|
89
90
|
{ timeout: false, passed: false, error: "empty result from child" }
|
|
90
91
|
else
|
|
91
|
-
{ timeout: false }.merge(Marshal.load(data))
|
|
92
|
+
{ timeout: false }.merge(Marshal.load(data))
|
|
92
93
|
end
|
|
93
94
|
else
|
|
94
95
|
terminate_child(pid)
|
|
@@ -113,7 +114,7 @@ class Evilution::Isolation::Fork
|
|
|
113
114
|
end
|
|
114
115
|
|
|
115
116
|
def terminate_child(pid)
|
|
116
|
-
::
|
|
117
|
+
Evilution::ProcessCleanup.safe_kill("TERM", pid)
|
|
117
118
|
_, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
|
|
118
119
|
return if status
|
|
119
120
|
|
|
@@ -121,8 +122,8 @@ class Evilution::Isolation::Fork
|
|
|
121
122
|
_, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
|
|
122
123
|
return if status
|
|
123
124
|
|
|
124
|
-
::
|
|
125
|
-
::
|
|
125
|
+
Evilution::ProcessCleanup.safe_kill("KILL", pid)
|
|
126
|
+
Evilution::ProcessCleanup.safe_wait(pid)
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def classify_status(result)
|
|
@@ -143,11 +144,15 @@ class Evilution::Isolation::Fork
|
|
|
143
144
|
status: status,
|
|
144
145
|
duration: duration,
|
|
145
146
|
test_command: result[:test_command],
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
memory: Evilution::Result::MemoryStats.from_fields(
|
|
148
|
+
child_rss_kb: result[:child_rss_kb],
|
|
149
|
+
parent_rss_kb: parent_rss_kb
|
|
150
|
+
),
|
|
151
|
+
error: Evilution::Result::ErrorInfo.from_fields(
|
|
152
|
+
message: result[:error],
|
|
153
|
+
klass: result[:error_class],
|
|
154
|
+
backtrace: result[:error_backtrace]
|
|
155
|
+
)
|
|
151
156
|
)
|
|
152
157
|
end
|
|
153
158
|
end
|
|
@@ -80,12 +80,16 @@ class Evilution::Isolation::InProcess
|
|
|
80
80
|
status: status,
|
|
81
81
|
duration: duration,
|
|
82
82
|
test_command: result[:test_command],
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
memory: Evilution::Result::MemoryStats.from_fields(
|
|
84
|
+
child_rss_kb: rss_after,
|
|
85
|
+
memory_delta_kb: memory_delta_kb,
|
|
86
|
+
parent_rss_kb: rss_before
|
|
87
|
+
),
|
|
88
|
+
error: Evilution::Result::ErrorInfo.from_fields(
|
|
89
|
+
message: result[:error],
|
|
90
|
+
klass: result[:error_class],
|
|
91
|
+
backtrace: result[:error_backtrace]
|
|
92
|
+
)
|
|
89
93
|
)
|
|
90
94
|
end
|
|
91
95
|
end
|
|
@@ -70,7 +70,6 @@ class Evilution::MCP::InfoTool < MCP::Tool
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
class << self
|
|
73
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
|
74
73
|
def call(server_context:, action: nil, files: nil, target: nil, spec: nil, integration: nil, skip_config: nil)
|
|
75
74
|
return ResponseFormatter.error("config_error", "action is required") unless action
|
|
76
75
|
return ResponseFormatter.error("config_error", "unknown action: #{action}") unless ACTIONS.key?(action)
|
|
@@ -84,7 +83,6 @@ class Evilution::MCP::InfoTool < MCP::Tool
|
|
|
84
83
|
rescue Evilution::Error => e
|
|
85
84
|
ResponseFormatter.error_for(e)
|
|
86
85
|
end
|
|
87
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
|
88
86
|
end
|
|
89
87
|
end
|
|
90
88
|
|
|
@@ -10,15 +10,19 @@ module Evilution::MCP::MutateTool::ProgressStreamer
|
|
|
10
10
|
|
|
11
11
|
suggestion = Evilution::Reporter::Suggestion.new(suggest_tests: true, integration: integration)
|
|
12
12
|
survivor_index = 0
|
|
13
|
+
disabled = false
|
|
13
14
|
|
|
14
15
|
proc do |result|
|
|
15
16
|
next unless result.survived?
|
|
17
|
+
next if disabled
|
|
16
18
|
|
|
17
19
|
begin
|
|
18
20
|
survivor_index += 1
|
|
19
21
|
detail = build_suggestion_detail(result.mutation, suggestion)
|
|
20
22
|
server_context.report_progress(survivor_index, message: ::JSON.generate(detail))
|
|
21
|
-
rescue StandardError
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
warn "[evilution] progress stream disabled after error: #{e.class}: #{e.message}"
|
|
25
|
+
disabled = true
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
@@ -51,7 +51,6 @@ class Evilution::MCP::SessionTool < MCP::Tool
|
|
|
51
51
|
VALID_ACTIONS = %w[list show diff].freeze
|
|
52
52
|
|
|
53
53
|
class << self
|
|
54
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
|
55
54
|
def call(server_context:, action: nil, results_dir: nil, limit: nil, path: nil, base: nil, head: nil)
|
|
56
55
|
return error_response("config_error", "action is required") unless action
|
|
57
56
|
return error_response("config_error", "unknown action: #{action}") unless VALID_ACTIONS.include?(action)
|
|
@@ -62,7 +61,6 @@ class Evilution::MCP::SessionTool < MCP::Tool
|
|
|
62
61
|
when "diff" then diff_action(base: base, head: head, results_dir: results_dir)
|
|
63
62
|
end
|
|
64
63
|
end
|
|
65
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
|
66
64
|
|
|
67
65
|
private
|
|
68
66
|
|
data/lib/evilution/mutation.rb
CHANGED
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "diff/lcs"
|
|
4
|
+
require_relative "../evilution"
|
|
4
5
|
|
|
5
6
|
class Evilution::Mutation
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
parse_status: :ok)
|
|
14
|
-
# rubocop:enable Metrics/ParameterLists
|
|
7
|
+
Sources = Data.define(:original, :mutated)
|
|
8
|
+
Slice = Data.define(:original, :mutated)
|
|
9
|
+
Location = Data.define(:file_path, :line, :column)
|
|
10
|
+
|
|
11
|
+
attr_reader :subject, :operator_name, :parse_status, :location
|
|
12
|
+
|
|
13
|
+
def initialize(subject:, operator_name:, sources:, location:, slice: nil, parse_status: :ok)
|
|
15
14
|
@subject = subject
|
|
16
15
|
@operator_name = operator_name
|
|
17
|
-
@
|
|
18
|
-
@
|
|
19
|
-
@
|
|
20
|
-
@mutated_slice = mutated_slice
|
|
21
|
-
@file_path = file_path
|
|
22
|
-
@line = line
|
|
23
|
-
@column = column
|
|
16
|
+
@sources = sources
|
|
17
|
+
@location = location
|
|
18
|
+
@slice = slice
|
|
24
19
|
@parse_status = parse_status
|
|
25
20
|
@diff = nil
|
|
26
21
|
end
|
|
27
22
|
|
|
23
|
+
def original_source
|
|
24
|
+
@sources&.original
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def mutated_source
|
|
28
|
+
@sources&.mutated
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def original_slice
|
|
32
|
+
@slice&.original
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mutated_slice
|
|
36
|
+
@slice&.mutated
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def file_path
|
|
40
|
+
@location.file_path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def line
|
|
44
|
+
@location.line
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def column
|
|
48
|
+
@location.column
|
|
49
|
+
end
|
|
50
|
+
|
|
28
51
|
def unparseable?
|
|
29
52
|
@parse_status == :unparseable
|
|
30
53
|
end
|
|
@@ -41,8 +64,11 @@ class Evilution::Mutation
|
|
|
41
64
|
|
|
42
65
|
def strip_sources!
|
|
43
66
|
diff # ensure diff is cached before clearing sources
|
|
44
|
-
@
|
|
45
|
-
|
|
67
|
+
@sources = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_s
|
|
71
|
+
"#{operator_name}: #{file_path}:#{line}"
|
|
46
72
|
end
|
|
47
73
|
|
|
48
74
|
private
|
|
@@ -67,10 +93,10 @@ class Evilution::Mutation
|
|
|
67
93
|
end
|
|
68
94
|
|
|
69
95
|
def compute_unified_diff
|
|
70
|
-
return nil if @
|
|
96
|
+
return nil if @slice.nil?
|
|
71
97
|
|
|
72
|
-
original_lines = @
|
|
73
|
-
mutated_lines = @
|
|
98
|
+
original_lines = @slice.original.lines
|
|
99
|
+
mutated_lines = @slice.mutated.lines
|
|
74
100
|
body = ::Diff::LCS.sdiff(original_lines, mutated_lines).map { |c| format_sdiff_change(c) }.join("\n")
|
|
75
101
|
[
|
|
76
102
|
"--- a/#{file_path}",
|
|
@@ -88,10 +114,4 @@ class Evilution::Mutation
|
|
|
88
114
|
when "!" then "-#{change.old_element.chomp}\n+#{change.new_element.chomp}"
|
|
89
115
|
end
|
|
90
116
|
end
|
|
91
|
-
|
|
92
|
-
public
|
|
93
|
-
|
|
94
|
-
def to_s
|
|
95
|
-
"#{operator_name}: #{file_path}:#{line}"
|
|
96
|
-
end
|
|
97
117
|
end
|
|
@@ -45,14 +45,14 @@ class Evilution::Mutator::Base < Prism::Visitor
|
|
|
45
45
|
@mutations << Evilution::Mutation.new(
|
|
46
46
|
subject: @subject,
|
|
47
47
|
operator_name: self.class.operator_name,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
sources: Evilution::Mutation::Sources.new(original: @file_source, mutated: mutated_source),
|
|
49
|
+
slice: Evilution::Mutation::Slice.new(original: original_slice, mutated: mutated_slice),
|
|
50
|
+
location: Evilution::Mutation::Location.new(
|
|
51
|
+
file_path: @subject.file_path,
|
|
52
|
+
line: node.location.start_line,
|
|
53
|
+
column: node.location.start_column
|
|
54
|
+
),
|
|
55
|
+
parse_status: surgery.status
|
|
56
56
|
)
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../operator"
|
|
4
|
+
|
|
5
|
+
class Evilution::Mutator::Operator::PredicateToNil < Evilution::Mutator::Base
|
|
6
|
+
def visit_call_node(node)
|
|
7
|
+
if node.name.to_s.end_with?("?")
|
|
8
|
+
loc = node.location
|
|
9
|
+
|
|
10
|
+
add_mutation(
|
|
11
|
+
offset: loc.start_offset,
|
|
12
|
+
length: loc.length,
|
|
13
|
+
replacement: "nil",
|
|
14
|
+
node: node
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
require_relative "../mutator"
|
|
4
4
|
|
|
5
5
|
class Evilution::Mutator::Registry
|
|
6
|
+
STRICT_EXTRA_OPERATORS = [
|
|
7
|
+
Evilution::Mutator::Operator::PredicateToNil
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
def self.for_profile(profile)
|
|
11
|
+
unless profile.is_a?(Symbol) || profile.is_a?(String)
|
|
12
|
+
raise ArgumentError, "unknown profile: #{profile.inspect} (expected :default or :strict)"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
case profile.to_sym
|
|
16
|
+
when :default then default
|
|
17
|
+
when :strict
|
|
18
|
+
registry = default
|
|
19
|
+
STRICT_EXTRA_OPERATORS.each { |op| registry.register(op) }
|
|
20
|
+
registry
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "unknown profile: #{profile.inspect} (expected :default or :strict)"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
6
26
|
def self.default
|
|
7
27
|
registry = new
|
|
8
28
|
[
|
|
@@ -10,12 +10,16 @@ module Evilution::Parallel::WorkQueue::Channel::Frame
|
|
|
10
10
|
[payload.bytesize].pack("N") + payload
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Marshal.load is safe here: payload originates from a sibling worker the
|
|
14
|
+
# parent itself forked, transferred over a private pipe inside our process
|
|
15
|
+
# tree. No external/untrusted input ever reaches this code. See
|
|
16
|
+
# .rubocop.yml (Security/MarshalLoad) for the full rationale.
|
|
13
17
|
def decode(header, payload)
|
|
14
18
|
return nil if header.nil? || header.bytesize < 4
|
|
15
19
|
|
|
16
20
|
length = header.unpack1("N")
|
|
17
21
|
return nil if payload.nil? || payload.bytesize < length
|
|
18
22
|
|
|
19
|
-
Marshal.load(payload)
|
|
23
|
+
Marshal.load(payload)
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -36,7 +36,7 @@ module Evilution::Parallel::WorkQueue::Worker::Loop
|
|
|
36
36
|
result = block.call(item)
|
|
37
37
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
38
38
|
Evilution::Parallel::WorkQueue::Channel.write(res_io, [index, :ok, result])
|
|
39
|
-
rescue
|
|
39
|
+
rescue StandardError, ScriptError, SystemStackError, NoMemoryError, SecurityError => e
|
|
40
40
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
41
41
|
Evilution::Parallel::WorkQueue::Channel.write(res_io, [index, :error, e])
|
|
42
42
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version"
|
|
4
|
+
|
|
5
|
+
module Evilution::ProcessCleanup
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def safe_kill(signal, pid)
|
|
9
|
+
::Process.kill(signal, pid)
|
|
10
|
+
rescue Errno::ESRCH
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def safe_wait(pid)
|
|
15
|
+
::Process.wait(pid)
|
|
16
|
+
rescue Errno::ECHILD
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../reporter"
|
|
4
4
|
require_relative "suggestion"
|
|
5
|
-
require_relative "html/escape"
|
|
6
|
-
require_relative "html/baseline_keys"
|
|
7
|
-
require_relative "html/report"
|
|
8
5
|
|
|
9
6
|
class Evilution::Reporter::HTML
|
|
7
|
+
autoload :Escape, "evilution/reporter/html/escape"
|
|
8
|
+
autoload :BaselineKeys, "evilution/reporter/html/baseline_keys"
|
|
9
|
+
autoload :Section, "evilution/reporter/html/section"
|
|
10
|
+
autoload :Sections, "evilution/reporter/html/sections"
|
|
11
|
+
autoload :Stylesheet, "evilution/reporter/html/stylesheet"
|
|
12
|
+
autoload :DiffFormatter, "evilution/reporter/html/diff_formatter"
|
|
13
|
+
autoload :Report, "evilution/reporter/html/report"
|
|
14
|
+
|
|
10
15
|
def initialize(baseline: nil, integration: :rspec)
|
|
11
16
|
@suggestion = Evilution::Reporter::Suggestion.new(integration: integration)
|
|
12
17
|
@baseline = baseline
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../suggestion"
|
|
4
|
-
|
|
5
|
-
# rubocop:disable Style/OneClassPerFile
|
|
6
|
-
module Evilution::Reporter::Suggestion::Templates
|
|
7
|
-
end
|
|
4
|
+
require_relative "templates"
|
|
8
5
|
|
|
9
6
|
class Evilution::Reporter::Suggestion::Registry
|
|
10
|
-
# rubocop:enable Style/OneClassPerFile
|
|
11
7
|
def self.default
|
|
12
8
|
return @default if @default
|
|
13
9
|
|