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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +5 -0
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +1 -0
  5. data/lib/evilution/cli/parser/command_extractor.rb +77 -0
  6. data/lib/evilution/cli/parser/file_args.rb +41 -0
  7. data/lib/evilution/cli/parser/options_builder.rb +103 -0
  8. data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
  9. data/lib/evilution/cli/parser.rb +27 -196
  10. data/lib/evilution/config.rb +14 -1
  11. data/lib/evilution/integration/base.rb +11 -57
  12. data/lib/evilution/integration/minitest.rb +16 -3
  13. data/lib/evilution/integration/rspec.rb +19 -7
  14. data/lib/evilution/isolation/fork.rb +1 -0
  15. data/lib/evilution/isolation/in_process.rb +1 -0
  16. data/lib/evilution/reporter/cli.rb +2 -1
  17. data/lib/evilution/reporter/html/assets/style.css +68 -0
  18. data/lib/evilution/reporter/html/baseline_keys.rb +28 -0
  19. data/lib/evilution/reporter/html/diff_formatter.rb +27 -0
  20. data/lib/evilution/reporter/html/escape.rb +12 -0
  21. data/lib/evilution/reporter/html/namespace.rb +11 -0
  22. data/lib/evilution/reporter/html/report.rb +68 -0
  23. data/lib/evilution/reporter/html/section.rb +21 -0
  24. data/lib/evilution/reporter/html/sections/baseline_comparison.rb +46 -0
  25. data/lib/evilution/reporter/html/sections/error_details.rb +30 -0
  26. data/lib/evilution/reporter/html/sections/error_entry.rb +22 -0
  27. data/lib/evilution/reporter/html/sections/file_section.rb +47 -0
  28. data/lib/evilution/reporter/html/sections/header.rb +29 -0
  29. data/lib/evilution/reporter/html/sections/mutation_map.rb +32 -0
  30. data/lib/evilution/reporter/html/sections/summary_cards.rb +11 -0
  31. data/lib/evilution/reporter/html/sections/survived_details.rb +35 -0
  32. data/lib/evilution/reporter/html/sections/survived_entry.rb +36 -0
  33. data/lib/evilution/reporter/html/sections/truncation_notice.rb +17 -0
  34. data/lib/evilution/reporter/html/sections.rb +4 -0
  35. data/lib/evilution/reporter/html/stylesheet.rb +14 -0
  36. data/lib/evilution/reporter/html/templates/baseline_comparison.html.erb +8 -0
  37. data/lib/evilution/reporter/html/templates/error_details.html.erb +6 -0
  38. data/lib/evilution/reporter/html/templates/error_entry.html.erb +10 -0
  39. data/lib/evilution/reporter/html/templates/file_section.html.erb +9 -0
  40. data/lib/evilution/reporter/html/templates/header.html.erb +4 -0
  41. data/lib/evilution/reporter/html/templates/mutation_map.html.erb +6 -0
  42. data/lib/evilution/reporter/html/templates/report.html.erb +17 -0
  43. data/lib/evilution/reporter/html/templates/summary_cards.html.erb +23 -0
  44. data/lib/evilution/reporter/html/templates/survived_details.html.erb +21 -0
  45. data/lib/evilution/reporter/html/templates/survived_entry.html.erb +8 -0
  46. data/lib/evilution/reporter/html/templates/truncation_notice.html.erb +1 -0
  47. data/lib/evilution/reporter/html.rb +11 -390
  48. data/lib/evilution/reporter/json.rb +12 -8
  49. data/lib/evilution/result/mutation_result.rb +5 -1
  50. data/lib/evilution/result/summary.rb +9 -1
  51. data/lib/evilution/runner/baseline_runner.rb +71 -0
  52. data/lib/evilution/runner/diagnostics.rb +105 -0
  53. data/lib/evilution/runner/isolation_resolver.rb +134 -0
  54. data/lib/evilution/runner/mutation_executor.rb +255 -0
  55. data/lib/evilution/runner/mutation_planner.rb +126 -0
  56. data/lib/evilution/runner/report_publisher.rb +60 -0
  57. data/lib/evilution/runner/subject_pipeline.rb +121 -0
  58. data/lib/evilution/runner.rb +57 -694
  59. data/lib/evilution/version.rb +1 -1
  60. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../sections"
4
+
5
+ class Evilution::Reporter::HTML::Sections::SummaryCards < Evilution::Reporter::HTML::Section
6
+ template "summary_cards"
7
+
8
+ def initialize(summary)
9
+ @summary = summary
10
+ end
11
+ 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "namespace"
4
+ require_relative "section"
@@ -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,6 @@
1
+ <div class="error-details">
2
+ <h3>Errors (<%= errored.length %>)</h3>
3
+ <%- sorted.each do |r| -%>
4
+ <%= render_entry(r) %>
5
+ <%- end -%>
6
+ </div>
@@ -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,4 @@
1
+ <header>
2
+ <h1>Evilution <span class="version">v<%= h(Evilution::VERSION) %></span></h1>
3
+ <div class="score-badge <%= score_css_class %>"><%= score_pct %></div>
4
+ </header>
@@ -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>