evilution 0.23.0 → 0.25.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 +210 -0
- data/CHANGELOG.md +51 -0
- data/README.md +81 -4
- data/exe/evil +6 -0
- data/lib/evilution/ast/source_surgeon.rb +15 -1
- data/lib/evilution/cli/commands/compare.rb +68 -0
- data/lib/evilution/cli/parser/command_extractor.rb +78 -0
- data/lib/evilution/cli/parser/file_args.rb +41 -0
- data/lib/evilution/cli/parser/options_builder.rb +123 -0
- data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
- data/lib/evilution/cli/parser.rb +27 -196
- data/lib/evilution/cli/printers/compare.rb +159 -0
- data/lib/evilution/cli.rb +1 -0
- data/lib/evilution/compare/categorizer.rb +109 -0
- data/lib/evilution/compare/detector.rb +21 -0
- data/lib/evilution/compare/fingerprint.rb +83 -0
- data/lib/evilution/compare/normalizer.rb +106 -0
- data/lib/evilution/compare/record.rb +16 -0
- data/lib/evilution/compare.rb +15 -0
- data/lib/evilution/config.rb +178 -3
- data/lib/evilution/example_filter.rb +143 -0
- data/lib/evilution/integration/base.rb +11 -57
- data/lib/evilution/integration/crash_detector.rb +5 -2
- data/lib/evilution/integration/minitest.rb +25 -7
- data/lib/evilution/integration/minitest_crash_detector.rb +5 -2
- data/lib/evilution/integration/rspec.rb +99 -12
- data/lib/evilution/isolation/fork.rb +26 -0
- data/lib/evilution/isolation/in_process.rb +1 -0
- data/lib/evilution/mcp/info_tool.rb +77 -5
- data/lib/evilution/mcp/mutate_tool/config_builder.rb +20 -0
- data/lib/evilution/mcp/mutate_tool/error_payload.rb +17 -0
- data/lib/evilution/mcp/mutate_tool/option_parser.rb +54 -0
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +37 -0
- data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +31 -0
- data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +52 -0
- data/lib/evilution/mcp/mutate_tool.rb +34 -186
- data/lib/evilution/mutation.rb +43 -3
- data/lib/evilution/mutator/base.rb +39 -1
- data/lib/evilution/mutator/operator/argument_nil_substitution.rb +5 -1
- data/lib/evilution/mutator/operator/argument_removal.rb +5 -1
- data/lib/evilution/parallel/work_queue.rb +149 -31
- data/lib/evilution/parallel_db_warning.rb +68 -0
- data/lib/evilution/reporter/cli.rb +38 -11
- data/lib/evilution/reporter/html/assets/style.css +85 -0
- data/lib/evilution/reporter/html/baseline_keys.rb +28 -0
- data/lib/evilution/reporter/html/diff_formatter.rb +27 -0
- data/lib/evilution/reporter/html/escape.rb +12 -0
- data/lib/evilution/reporter/html/namespace.rb +11 -0
- data/lib/evilution/reporter/html/report.rb +68 -0
- data/lib/evilution/reporter/html/section.rb +21 -0
- data/lib/evilution/reporter/html/sections/baseline_comparison.rb +46 -0
- data/lib/evilution/reporter/html/sections/error_details.rb +30 -0
- data/lib/evilution/reporter/html/sections/error_entry.rb +22 -0
- data/lib/evilution/reporter/html/sections/file_section.rb +62 -0
- data/lib/evilution/reporter/html/sections/header.rb +29 -0
- data/lib/evilution/reporter/html/sections/mutation_map.rb +32 -0
- data/lib/evilution/reporter/html/sections/neutral_details.rb +25 -0
- data/lib/evilution/reporter/html/sections/summary_cards.rb +11 -0
- data/lib/evilution/reporter/html/sections/survived_details.rb +35 -0
- data/lib/evilution/reporter/html/sections/survived_entry.rb +36 -0
- data/lib/evilution/reporter/html/sections/truncation_notice.rb +17 -0
- data/lib/evilution/reporter/html/sections/unparseable_details.rb +25 -0
- data/lib/evilution/reporter/html/sections/unresolved_details.rb +25 -0
- data/lib/evilution/reporter/html/sections.rb +4 -0
- data/lib/evilution/reporter/html/stylesheet.rb +14 -0
- data/lib/evilution/reporter/html/templates/baseline_comparison.html.erb +8 -0
- data/lib/evilution/reporter/html/templates/error_details.html.erb +6 -0
- data/lib/evilution/reporter/html/templates/error_entry.html.erb +10 -0
- data/lib/evilution/reporter/html/templates/file_section.html.erb +12 -0
- data/lib/evilution/reporter/html/templates/header.html.erb +4 -0
- data/lib/evilution/reporter/html/templates/mutation_map.html.erb +6 -0
- data/lib/evilution/reporter/html/templates/neutral_details.html.erb +14 -0
- data/lib/evilution/reporter/html/templates/report.html.erb +17 -0
- data/lib/evilution/reporter/html/templates/summary_cards.html.erb +26 -0
- data/lib/evilution/reporter/html/templates/survived_details.html.erb +21 -0
- data/lib/evilution/reporter/html/templates/survived_entry.html.erb +8 -0
- data/lib/evilution/reporter/html/templates/truncation_notice.html.erb +1 -0
- data/lib/evilution/reporter/html/templates/unparseable_details.html.erb +11 -0
- data/lib/evilution/reporter/html/templates/unresolved_details.html.erb +11 -0
- data/lib/evilution/reporter/html.rb +11 -390
- data/lib/evilution/reporter/json.rb +19 -9
- data/lib/evilution/reporter/suggestion/diff_helpers.rb +28 -0
- data/lib/evilution/reporter/suggestion/registry.rb +64 -0
- data/lib/evilution/reporter/suggestion/templates/generic.rb +55 -0
- data/lib/evilution/reporter/suggestion/templates/minitest.rb +659 -0
- data/lib/evilution/reporter/suggestion/templates/rspec.rb +613 -0
- data/lib/evilution/reporter/suggestion.rb +8 -1327
- data/lib/evilution/result/mutation_result.rb +9 -1
- data/lib/evilution/result/summary.rb +21 -1
- data/lib/evilution/runner/baseline_runner.rb +92 -0
- data/lib/evilution/runner/diagnostics.rb +105 -0
- data/lib/evilution/runner/isolation_resolver.rb +134 -0
- data/lib/evilution/runner/mutation_executor.rb +325 -0
- data/lib/evilution/runner/mutation_planner.rb +126 -0
- data/lib/evilution/runner/report_publisher.rb +60 -0
- data/lib/evilution/runner/subject_pipeline.rb +121 -0
- data/lib/evilution/runner.rb +61 -692
- data/lib/evilution/source_ast_cache.rb +39 -0
- data/lib/evilution/spec_ast_cache.rb +166 -0
- data/lib/evilution/spec_resolver.rb +6 -1
- data/lib/evilution/spec_selector.rb +39 -0
- data/lib/evilution/temp_dir_tracker.rb +23 -3
- data/lib/evilution/version.rb +1 -1
- data/script/memory_check +7 -5
- metadata +75 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::BaselineComparison < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "baseline_comparison"
|
|
7
|
+
|
|
8
|
+
def self.render_if(baseline, summary)
|
|
9
|
+
return "" unless baseline
|
|
10
|
+
|
|
11
|
+
new(baseline, summary).render
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(baseline, summary)
|
|
15
|
+
@baseline = baseline
|
|
16
|
+
@summary = summary
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def base_score
|
|
22
|
+
(@baseline["summary"] || {})["score"] || 0.0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def head_score
|
|
26
|
+
@summary.score
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def delta
|
|
30
|
+
head_score - base_score
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def delta_str
|
|
34
|
+
format("%+.2f%%", delta * 100)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delta_class
|
|
38
|
+
if delta.positive?
|
|
39
|
+
"delta-positive"
|
|
40
|
+
elsif delta.negative?
|
|
41
|
+
"delta-negative"
|
|
42
|
+
else
|
|
43
|
+
"delta-neutral"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "error_entry"
|
|
5
|
+
|
|
6
|
+
class Evilution::Reporter::HTML::Sections::ErrorDetails < Evilution::Reporter::HTML::Section
|
|
7
|
+
template "error_details"
|
|
8
|
+
|
|
9
|
+
def self.render_if(errored)
|
|
10
|
+
return "" if errored.empty?
|
|
11
|
+
|
|
12
|
+
new(errored).render
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(errored)
|
|
16
|
+
@errored = errored
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :errored
|
|
22
|
+
|
|
23
|
+
def sorted
|
|
24
|
+
errored.sort_by { |r| [r.mutation.operator_name, r.mutation.line] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render_entry(result)
|
|
28
|
+
Evilution::Reporter::HTML::Sections::ErrorEntry.new(result).render
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "../diff_formatter"
|
|
5
|
+
|
|
6
|
+
class Evilution::Reporter::HTML::Sections::ErrorEntry < Evilution::Reporter::HTML::Section
|
|
7
|
+
template "error_entry"
|
|
8
|
+
|
|
9
|
+
def initialize(result)
|
|
10
|
+
@result = result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
@result.error_message.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def diff_html
|
|
20
|
+
Evilution::Reporter::HTML::DiffFormatter.call(@result.mutation.diff)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "mutation_map"
|
|
5
|
+
require_relative "survived_details"
|
|
6
|
+
require_relative "error_details"
|
|
7
|
+
require_relative "neutral_details"
|
|
8
|
+
require_relative "unresolved_details"
|
|
9
|
+
require_relative "unparseable_details"
|
|
10
|
+
|
|
11
|
+
class Evilution::Reporter::HTML::Sections::FileSection < Evilution::Reporter::HTML::Section
|
|
12
|
+
template "file_section"
|
|
13
|
+
|
|
14
|
+
def initialize(path, results, suggestion:, baseline_keys:)
|
|
15
|
+
@path = path
|
|
16
|
+
@results = results
|
|
17
|
+
@suggestion = suggestion
|
|
18
|
+
@baseline_keys = baseline_keys
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def killed_count
|
|
24
|
+
@results.count(&:killed?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def survived_count
|
|
28
|
+
@results.count(&:survived?)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def total
|
|
32
|
+
@results.length
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def map_html
|
|
36
|
+
Evilution::Reporter::HTML::Sections::MutationMap.new(@results).render
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def survived_html
|
|
40
|
+
Evilution::Reporter::HTML::Sections::SurvivedDetails.render_if(
|
|
41
|
+
@results.select(&:survived?),
|
|
42
|
+
suggestion: @suggestion,
|
|
43
|
+
baseline_keys: @baseline_keys
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def error_html
|
|
48
|
+
Evilution::Reporter::HTML::Sections::ErrorDetails.render_if(@results.select(&:error?))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def neutral_html
|
|
52
|
+
Evilution::Reporter::HTML::Sections::NeutralDetails.render_if(@results.select(&:neutral?))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def unresolved_html
|
|
56
|
+
Evilution::Reporter::HTML::Sections::UnresolvedDetails.render_if(@results.select(&:unresolved?))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def unparseable_html
|
|
60
|
+
Evilution::Reporter::HTML::Sections::UnparseableDetails.render_if(@results.select(&:unparseable?))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "../../../version"
|
|
5
|
+
|
|
6
|
+
class Evilution::Reporter::HTML::Sections::Header < Evilution::Reporter::HTML::Section
|
|
7
|
+
template "header"
|
|
8
|
+
|
|
9
|
+
def initialize(summary)
|
|
10
|
+
@summary = summary
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def score_pct
|
|
16
|
+
format("%.2f%%", @summary.score * 100)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def score_css_class
|
|
20
|
+
score = @summary.score
|
|
21
|
+
if score >= 0.8
|
|
22
|
+
"score-high"
|
|
23
|
+
elsif score >= 0.5
|
|
24
|
+
"score-medium"
|
|
25
|
+
else
|
|
26
|
+
"score-low"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::MutationMap < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "mutation_map"
|
|
7
|
+
|
|
8
|
+
Entry = Struct.new(:line, :operator_name, :status, :title_attr)
|
|
9
|
+
|
|
10
|
+
def initialize(results)
|
|
11
|
+
@results = results
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def entries
|
|
17
|
+
@entries ||= @results.sort_by { |r| r.mutation.line }.map { |r| build_entry(r) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_entry(result)
|
|
21
|
+
title = normalize_title(result.error_message)
|
|
22
|
+
title_attr = title ? %( title="#{h(title)}") : ""
|
|
23
|
+
Entry.new(result.mutation.line, result.mutation.operator_name, result.status.to_s, title_attr)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def normalize_title(message)
|
|
27
|
+
return nil if message.nil?
|
|
28
|
+
|
|
29
|
+
normalized = message.gsub(/\s+/, " ").strip
|
|
30
|
+
normalized.empty? ? nil : normalized
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::NeutralDetails < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "neutral_details"
|
|
7
|
+
|
|
8
|
+
def self.render_if(neutral)
|
|
9
|
+
return "" if neutral.empty?
|
|
10
|
+
|
|
11
|
+
new(neutral).render
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(neutral)
|
|
15
|
+
@neutral = neutral
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :neutral
|
|
21
|
+
|
|
22
|
+
def sorted
|
|
23
|
+
neutral.sort_by { |r| [r.mutation.operator_name, r.mutation.line] }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "../../../result/coverage_gap_grouper"
|
|
5
|
+
require_relative "survived_entry"
|
|
6
|
+
|
|
7
|
+
class Evilution::Reporter::HTML::Sections::SurvivedDetails < Evilution::Reporter::HTML::Section
|
|
8
|
+
template "survived_details"
|
|
9
|
+
|
|
10
|
+
def self.render_if(survived, suggestion:, baseline_keys:)
|
|
11
|
+
return "" if survived.empty?
|
|
12
|
+
|
|
13
|
+
new(survived, suggestion: suggestion, baseline_keys: baseline_keys).render
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(survived, suggestion:, baseline_keys:)
|
|
17
|
+
@survived = survived
|
|
18
|
+
@suggestion = suggestion
|
|
19
|
+
@baseline_keys = baseline_keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def gaps
|
|
25
|
+
@gaps ||= Evilution::Result::CoverageGapGrouper.new.call(@survived)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render_entry(result)
|
|
29
|
+
Evilution::Reporter::HTML::Sections::SurvivedEntry.new(
|
|
30
|
+
result,
|
|
31
|
+
suggestion: @suggestion,
|
|
32
|
+
baseline_keys: @baseline_keys
|
|
33
|
+
).render
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
require_relative "../diff_formatter"
|
|
5
|
+
|
|
6
|
+
class Evilution::Reporter::HTML::Sections::SurvivedEntry < Evilution::Reporter::HTML::Section
|
|
7
|
+
template "survived_entry"
|
|
8
|
+
|
|
9
|
+
def initialize(result, suggestion:, baseline_keys:)
|
|
10
|
+
@result = result
|
|
11
|
+
@suggestion = suggestion
|
|
12
|
+
@baseline_keys = baseline_keys
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def regression?
|
|
18
|
+
@baseline_keys.regression?(@result.mutation)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def entry_class
|
|
22
|
+
regression? ? "survived-entry regression" : "survived-entry"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def regression_badge
|
|
26
|
+
regression? ? ' <span class="regression-badge">NEW REGRESSION</span>' : ""
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def suggestion_text
|
|
30
|
+
@suggestion.suggestion_for(@result.mutation)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def diff_html
|
|
34
|
+
Evilution::Reporter::HTML::DiffFormatter.call(@result.mutation.diff)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::TruncationNotice < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "truncation_notice"
|
|
7
|
+
|
|
8
|
+
def initialize(summary)
|
|
9
|
+
@summary = summary
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.render_if(summary)
|
|
13
|
+
return "" unless summary.truncated?
|
|
14
|
+
|
|
15
|
+
new(summary).render
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::UnparseableDetails < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "unparseable_details"
|
|
7
|
+
|
|
8
|
+
def self.render_if(unparseable)
|
|
9
|
+
return "" if unparseable.empty?
|
|
10
|
+
|
|
11
|
+
new(unparseable).render
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(unparseable)
|
|
15
|
+
@unparseable = unparseable
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :unparseable
|
|
21
|
+
|
|
22
|
+
def sorted
|
|
23
|
+
unparseable.sort_by { |r| [r.mutation.operator_name, r.mutation.line] }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../sections"
|
|
4
|
+
|
|
5
|
+
class Evilution::Reporter::HTML::Sections::UnresolvedDetails < Evilution::Reporter::HTML::Section
|
|
6
|
+
template "unresolved_details"
|
|
7
|
+
|
|
8
|
+
def self.render_if(unresolved)
|
|
9
|
+
return "" if unresolved.empty?
|
|
10
|
+
|
|
11
|
+
new(unresolved).render
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(unresolved)
|
|
15
|
+
@unresolved = unresolved
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :unresolved
|
|
21
|
+
|
|
22
|
+
def sorted
|
|
23
|
+
unresolved.sort_by { |r| [r.mutation.operator_name, r.mutation.line] }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "namespace"
|
|
4
|
+
|
|
5
|
+
module Evilution::Reporter::HTML::Stylesheet
|
|
6
|
+
PATH = File.expand_path("assets/style.css", __dir__)
|
|
7
|
+
CSS = File.read(PATH).freeze
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
"<style>\n#{CSS}</style>"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<section class="baseline-comparison">
|
|
2
|
+
<h2>Baseline Comparison</h2>
|
|
3
|
+
<div class="comparison-scores">
|
|
4
|
+
<span>Baseline: <%= format("%.2f%%", base_score * 100) %></span>
|
|
5
|
+
<span>Current: <%= format("%.2f%%", head_score * 100) %></span>
|
|
6
|
+
<span class="<%= delta_class %>">Delta: <%= delta_str %></span>
|
|
7
|
+
</div>
|
|
8
|
+
</section>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<div class="error-entry">
|
|
2
|
+
<div class="error-header">
|
|
3
|
+
<span class="operator"><%= h(@result.mutation.operator_name) %></span>
|
|
4
|
+
<span class="location"><%= h(@result.mutation.file_path) %>:<%= @result.mutation.line %></span>
|
|
5
|
+
</div>
|
|
6
|
+
<pre class="diff"><%= diff_html %></pre>
|
|
7
|
+
<%- unless message.empty? -%>
|
|
8
|
+
<pre class="error-message"><%= h(message) %></pre>
|
|
9
|
+
<%- end -%>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<section class="file-section">
|
|
2
|
+
<h2 class="file-header">
|
|
3
|
+
<span class="file-path"><%= h(@path) %></span>
|
|
4
|
+
<span class="file-stats"><%= killed_count %> killed / <%= survived_count %> survived / <%= total %> total</span>
|
|
5
|
+
</h2>
|
|
6
|
+
<div class="mutation-map"><%= map_html %></div>
|
|
7
|
+
<%= survived_html %>
|
|
8
|
+
<%= error_html %>
|
|
9
|
+
<%= neutral_html %>
|
|
10
|
+
<%= unresolved_html %>
|
|
11
|
+
<%= unparseable_html %>
|
|
12
|
+
</section>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%- entries.each_with_index do |entry, i| -%>
|
|
2
|
+
<div class="map-line <%= entry.status %>"<%= entry.title_attr %>>
|
|
3
|
+
<span class="line-number">line <%= entry.line %></span>
|
|
4
|
+
<span class="operator"><%= h(entry.operator_name) %></span>
|
|
5
|
+
<span class="status-badge <%= entry.status %>"><%= entry.status %></span>
|
|
6
|
+
</div><%= "\n" if i < entries.length - 1 %><%- end -%>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="neutral-details">
|
|
2
|
+
<h3>Neutral (<%= neutral.length %>) — test infra error, excluded from score</h3>
|
|
3
|
+
<ul>
|
|
4
|
+
<%- sorted.each do |r| -%>
|
|
5
|
+
<li>
|
|
6
|
+
<span class="operator"><%= h(r.mutation.operator_name) %></span>
|
|
7
|
+
<span class="location"><%= h(r.mutation.file_path) %>:<%= r.mutation.line %></span>
|
|
8
|
+
<%- if r.error_class -%>
|
|
9
|
+
<span class="error-class">(<%= h(r.error_class) %>)</span>
|
|
10
|
+
<%- end -%>
|
|
11
|
+
</li>
|
|
12
|
+
<%- end -%>
|
|
13
|
+
</ul>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Evilution Mutation Report</title>
|
|
7
|
+
<%= stylesheet %>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<%= header %>
|
|
11
|
+
<%= summary_cards %>
|
|
12
|
+
<%= baseline_comparison %>
|
|
13
|
+
<%= truncation_notice %>
|
|
14
|
+
<%= file_sections %>
|
|
15
|
+
<footer>Generated by Evilution v<%= h(Evilution::VERSION) %></footer>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<section class="summary-cards">
|
|
2
|
+
<div class="card"><span class="card-value"><%= @summary.total %></span><span class="card-label">Total</span></div>
|
|
3
|
+
<div class="card card-killed"><span class="card-value"><%= @summary.killed %></span><span class="card-label">Killed</span></div>
|
|
4
|
+
<div class="card card-survived"><span class="card-value"><%= @summary.survived %></span><span class="card-label">Survived</span></div>
|
|
5
|
+
<div class="card"><span class="card-value"><%= @summary.timed_out %></span><span class="card-label">Timed Out</span></div>
|
|
6
|
+
<div class="card"><span class="card-value"><%= @summary.errors %></span><span class="card-label">Errors</span></div>
|
|
7
|
+
<div class="card"><span class="card-value"><%= @summary.neutral %></span><span class="card-label">Neutral</span></div>
|
|
8
|
+
<div class="card"><span class="card-value"><%= @summary.equivalent %></span><span class="card-label">Equivalent</span></div>
|
|
9
|
+
<%- if @summary.unresolved.positive? -%>
|
|
10
|
+
<div class="card"><span class="card-value"><%= @summary.unresolved %></span><span class="card-label">Unresolved</span></div>
|
|
11
|
+
<%- end -%>
|
|
12
|
+
<%- if @summary.unparseable.positive? -%>
|
|
13
|
+
<div class="card"><span class="card-value"><%= @summary.unparseable %></span><span class="card-label">Unparseable</span></div>
|
|
14
|
+
<%- end -%>
|
|
15
|
+
<%- if @summary.skipped.positive? -%>
|
|
16
|
+
<div class="card"><span class="card-value"><%= @summary.skipped %></span><span class="card-label">Skipped</span></div>
|
|
17
|
+
<%- end -%>
|
|
18
|
+
<div class="card"><span class="card-value"><%= format("%.2f", @summary.duration) %>s</span><span class="card-label">Duration</span></div>
|
|
19
|
+
<%- if @summary.duration.positive? -%>
|
|
20
|
+
<div class="card"><span class="card-value"><%= format("%.1f%%", @summary.efficiency * 100) %></span><span class="card-label">Efficiency</span></div>
|
|
21
|
+
<div class="card"><span class="card-value"><%= format("%.2f", @summary.mutations_per_second) %>/s</span><span class="card-label">Rate</span></div>
|
|
22
|
+
<%- end -%>
|
|
23
|
+
<%- if @summary.peak_memory_mb -%>
|
|
24
|
+
<div class="card"><span class="card-value"><%= format("%.1f", @summary.peak_memory_mb) %> MB</span><span class="card-label">Peak Memory</span></div>
|
|
25
|
+
<%- end -%>
|
|
26
|
+
</section>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="survived-details">
|
|
2
|
+
<h3>Coverage Gaps (<%= gaps.length %>)</h3>
|
|
3
|
+
<%- gaps.each do |gap| -%>
|
|
4
|
+
<%- if gap.single? -%>
|
|
5
|
+
<%= render_entry(gap.mutation_results.first) %>
|
|
6
|
+
<%- else -%>
|
|
7
|
+
<div class="coverage-gap">
|
|
8
|
+
<div class="gap-header">
|
|
9
|
+
<span class="location"><%= h(gap.file_path) %>:<%= gap.line %> (<%= h(gap.subject_name) %>)</span>
|
|
10
|
+
<span class="gap-count"><%= gap.count %> mutations</span>
|
|
11
|
+
<%- gap.operator_names.each do |op| -%>
|
|
12
|
+
<span class="operator-tag"><%= h(op) %></span>
|
|
13
|
+
<%- end -%>
|
|
14
|
+
</div>
|
|
15
|
+
<%- gap.mutation_results.each do |r| -%>
|
|
16
|
+
<%= render_entry(r) %>
|
|
17
|
+
<%- end -%>
|
|
18
|
+
</div>
|
|
19
|
+
<%- end -%>
|
|
20
|
+
<%- end -%>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div class="<%= entry_class %>">
|
|
2
|
+
<div class="survived-header">
|
|
3
|
+
<span class="operator"><%= h(@result.mutation.operator_name) %><%= regression_badge %></span>
|
|
4
|
+
<span class="location"><%= h(@result.mutation.file_path) %>:<%= @result.mutation.line %></span>
|
|
5
|
+
</div>
|
|
6
|
+
<pre class="diff"><%= diff_html %></pre>
|
|
7
|
+
<div class="suggestion"><%= h(suggestion_text) %></div>
|
|
8
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="truncation-notice">Truncated: Stopped early due to --fail-fast</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="unparseable-details">
|
|
2
|
+
<h3>Unparseable (<%= unparseable.length %>) — mutated source did not parse</h3>
|
|
3
|
+
<ul>
|
|
4
|
+
<%- sorted.each do |r| -%>
|
|
5
|
+
<li>
|
|
6
|
+
<span class="operator"><%= h(r.mutation.operator_name) %></span>
|
|
7
|
+
<span class="location"><%= h(r.mutation.file_path) %>:<%= r.mutation.line %></span>
|
|
8
|
+
</li>
|
|
9
|
+
<%- end -%>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="unresolved-details">
|
|
2
|
+
<h3>Unresolved (<%= unresolved.length %>) — no test file resolved</h3>
|
|
3
|
+
<ul>
|
|
4
|
+
<%- sorted.each do |r| -%>
|
|
5
|
+
<li>
|
|
6
|
+
<span class="operator"><%= h(r.mutation.operator_name) %></span>
|
|
7
|
+
<span class="location"><%= h(r.mutation.file_path) %>:<%= r.mutation.line %></span>
|
|
8
|
+
</li>
|
|
9
|
+
<%- end -%>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|