evilution 0.23.0 → 0.24.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 +5 -0
- data/CHANGELOG.md +16 -0
- data/README.md +1 -0
- data/lib/evilution/cli/parser/command_extractor.rb +77 -0
- data/lib/evilution/cli/parser/file_args.rb +41 -0
- data/lib/evilution/cli/parser/options_builder.rb +103 -0
- data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
- data/lib/evilution/cli/parser.rb +27 -196
- data/lib/evilution/config.rb +14 -1
- data/lib/evilution/integration/base.rb +11 -57
- data/lib/evilution/integration/minitest.rb +16 -3
- data/lib/evilution/integration/rspec.rb +19 -7
- data/lib/evilution/isolation/fork.rb +1 -0
- data/lib/evilution/isolation/in_process.rb +1 -0
- data/lib/evilution/reporter/cli.rb +2 -1
- data/lib/evilution/reporter/html/assets/style.css +68 -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 +47 -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/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.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 +9 -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/report.html.erb +17 -0
- data/lib/evilution/reporter/html/templates/summary_cards.html.erb +23 -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.rb +11 -390
- data/lib/evilution/reporter/json.rb +12 -8
- data/lib/evilution/result/mutation_result.rb +5 -1
- data/lib/evilution/result/summary.rb +9 -1
- data/lib/evilution/runner/baseline_runner.rb +71 -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 +255 -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 +57 -694
- data/lib/evilution/version.rb +1 -1
- metadata +42 -1
|
@@ -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,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,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,9 @@
|
|
|
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
|
+
</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,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,23 @@
|
|
|
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.skipped.positive? -%>
|
|
13
|
+
<div class="card"><span class="card-value"><%= @summary.skipped %></span><span class="card-label">Skipped</span></div>
|
|
14
|
+
<%- end -%>
|
|
15
|
+
<div class="card"><span class="card-value"><%= format("%.2f", @summary.duration) %>s</span><span class="card-label">Duration</span></div>
|
|
16
|
+
<%- if @summary.duration.positive? -%>
|
|
17
|
+
<div class="card"><span class="card-value"><%= format("%.1f%%", @summary.efficiency * 100) %></span><span class="card-label">Efficiency</span></div>
|
|
18
|
+
<div class="card"><span class="card-value"><%= format("%.2f", @summary.mutations_per_second) %>/s</span><span class="card-label">Rate</span></div>
|
|
19
|
+
<%- end -%>
|
|
20
|
+
<%- if @summary.peak_memory_mb -%>
|
|
21
|
+
<div class="card"><span class="card-value"><%= format("%.1f", @summary.peak_memory_mb) %> MB</span><span class="card-label">Peak Memory</span></div>
|
|
22
|
+
<%- end -%>
|
|
23
|
+
</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>
|