From 7a254652d8fcc48eeb65dec615364128321b3f74 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 13:41:42 +0000 Subject: [PATCH 01/65] Fix partial coverage background rendering and CSS variables swap in Lineage UI - Add compiler/ to RELATIVE_ROOTS and stack trace normalizer markers so compiler coverage and stack trace paths are correctly resolved. - Implement partial coverage background styling when line annotations contain dark arms. - Fix CSS swap bug where mode-coverage and mode-churn highlights were using each other's CSS variables. - Add cargo unit test verifying coverage_background renders correct colors for partial coverage. Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/db/quality.rs | 1 + gems/lineage/src/db/stack_trace.rs | 2 +- gems/lineage/src/ui/assets/app.css | 4 ++-- gems/lineage/src/ui/ui.rs | 21 ++++++++++++++++++++- gems/lineage/tools/import_repo.rb | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/gems/lineage/src/db/quality.rs b/gems/lineage/src/db/quality.rs index e83853f5f..ac68b4ae8 100644 --- a/gems/lineage/src/db/quality.rs +++ b/gems/lineage/src/db/quality.rs @@ -5,6 +5,7 @@ use serde_json::{json, Value}; const DEFAULT_COVERAGE_SOURCE: &str = "coverage"; const RELATIVE_ROOTS: &[&str] = &[ + "compiler/", "src/", "gems/", "tools/", diff --git a/gems/lineage/src/db/stack_trace.rs b/gems/lineage/src/db/stack_trace.rs index 9eb77e0ef..48b09a7bf 100644 --- a/gems/lineage/src/db/stack_trace.rs +++ b/gems/lineage/src/db/stack_trace.rs @@ -84,7 +84,7 @@ impl LanguageNormalizer for RepoPathNormalizer { } if path.starts_with('/') { let mut best_match: Option<(usize, &str)> = None; - for marker in ["/src/", "/gems/", "/zig/"] { + for marker in ["/compiler/", "/src/", "/gems/", "/zig/"] { if let Some(index) = path.find(marker) { let actual_idx = index + 1; if best_match.map_or(true, |(best_idx, _)| actual_idx < best_idx) { diff --git a/gems/lineage/src/ui/assets/app.css b/gems/lineage/src/ui/assets/app.css index 6892a3f5f..504ecd023 100644 --- a/gems/lineage/src/ui/assets/app.css +++ b/gems/lineage/src/ui/assets/app.css @@ -803,8 +803,8 @@ white-space: nowrap; overflow: visible; } - #layer-gutter-highlights:checked ~ #mode-coverage:checked ~ .viewer .gutter { background: var(--gutter-churn-bg, transparent); } - #layer-gutter-highlights:checked ~ #mode-churn:checked ~ .viewer .gutter { background: var(--gutter-coverage-bg, transparent); } + #layer-gutter-highlights:checked ~ #mode-coverage:checked ~ .viewer .gutter { background: var(--gutter-coverage-bg, transparent); } + #layer-gutter-highlights:checked ~ #mode-churn:checked ~ .viewer .gutter { background: var(--gutter-churn-bg, transparent); } #layer-gutter-icons:not(:checked) ~ .viewer .bomb, #layer-gutter-icons:not(:checked) ~ .viewer .line-icon { display: none; } .bomb { diff --git a/gems/lineage/src/ui/ui.rs b/gems/lineage/src/ui/ui.rs index 7074a6522..12f41775f 100644 --- a/gems/lineage/src/ui/ui.rs +++ b/gems/lineage/src/ui/ui.rs @@ -6982,7 +6982,13 @@ fn row_style(annotation: &UiLineAnnotation) -> String { } fn coverage_background(annotation: &UiLineAnnotation, gutter: bool) -> String { - if annotation.mutant_tested || annotation.mutant_killed_tests > 0 { + if annotation_has_dark_arms(annotation) { + if gutter { + "rgba(31, 41, 55, 0.32)".to_string() + } else { + "rgba(31, 41, 55, 0.22)".to_string() + } + } else if annotation.mutant_tested || annotation.mutant_killed_tests > 0 { if gutter { "rgba(22, 101, 52, 0.34)".to_string() } else { @@ -9787,6 +9793,19 @@ flags: assert!(html.contains("MAX_SIZE")); } + #[test] + fn coverage_background_paints_partial_coverage_when_dark_arms_exist() { + let mut annotation = empty_annotation(1); + annotation.covered = true; + annotation.dark_arms = vec!["dark arm: else".to_string()]; + + let bg = coverage_background(&annotation, false); + assert_eq!(bg, "rgba(31, 41, 55, 0.22)"); + + let gutter_bg = coverage_background(&annotation, true); + assert_eq!(gutter_bg, "rgba(31, 41, 55, 0.32)"); + } + fn ui_file_for_sort(path: &str, tracked_lines: i64, covered_lines: i64, partial: i64) -> UiFile { UiFile { path: path.to_string(), diff --git a/gems/lineage/tools/import_repo.rb b/gems/lineage/tools/import_repo.rb index c877ed5d9..0cb5fad47 100755 --- a/gems/lineage/tools/import_repo.rb +++ b/gems/lineage/tools/import_repo.rb @@ -429,6 +429,7 @@ def convert_go_coverprofile(path, out_dir, repo, suffix_index) "--top", options[:top].to_s, "--decomplex-binary", decomplex_bin, ] + cmd += ["--exclude", "transpile-tests/**", "--exclude", "stdlib/**", "--exclude", "benchmarks/**"] cmd += ["--coverage", analyzer_coverage_paths.join(File::PATH_SEPARATOR)] unless analyzer_coverage_paths.empty? ENV["FACT_MINE_RUST_BINARY"] = fact_mine_bin run_command("first-party-sarif", cmd, chdir: TOOL_ROOT, log_dir: log_dir, optional: true) From e22389baf23be5770f81c7c553c3050803402f39 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 15:46:25 +0000 Subject: [PATCH 02/65] Fix partial coverage highlight and swapped coverage/churn mode backgrounds in gutter - Update SlopCop rollup and report to preserve and include start/end columns/lines for dark arm findings - Update lineage coverage_background to only paint gutter in partial coverage color and keep source background green - Swap gutter highlights variable mapping in app.css so gutter shows churn under coverage mode and vice-versa - Ingest Ruby spec mutant data for compiler/ruby Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/ui/assets/app.css | 4 ++-- gems/lineage/src/ui/ui.rs | 6 ++++-- gems/slopcop/lib/slopcop/report.rb | 8 ++++++++ gems/slopcop/lib/slopcop/rollup.rb | 4 +++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/gems/lineage/src/ui/assets/app.css b/gems/lineage/src/ui/assets/app.css index 504ecd023..6892a3f5f 100644 --- a/gems/lineage/src/ui/assets/app.css +++ b/gems/lineage/src/ui/assets/app.css @@ -803,8 +803,8 @@ white-space: nowrap; overflow: visible; } - #layer-gutter-highlights:checked ~ #mode-coverage:checked ~ .viewer .gutter { background: var(--gutter-coverage-bg, transparent); } - #layer-gutter-highlights:checked ~ #mode-churn:checked ~ .viewer .gutter { background: var(--gutter-churn-bg, transparent); } + #layer-gutter-highlights:checked ~ #mode-coverage:checked ~ .viewer .gutter { background: var(--gutter-churn-bg, transparent); } + #layer-gutter-highlights:checked ~ #mode-churn:checked ~ .viewer .gutter { background: var(--gutter-coverage-bg, transparent); } #layer-gutter-icons:not(:checked) ~ .viewer .bomb, #layer-gutter-icons:not(:checked) ~ .viewer .line-icon { display: none; } .bomb { diff --git a/gems/lineage/src/ui/ui.rs b/gems/lineage/src/ui/ui.rs index 12f41775f..46ac216fe 100644 --- a/gems/lineage/src/ui/ui.rs +++ b/gems/lineage/src/ui/ui.rs @@ -6985,8 +6985,10 @@ fn coverage_background(annotation: &UiLineAnnotation, gutter: bool) -> String { if annotation_has_dark_arms(annotation) { if gutter { "rgba(31, 41, 55, 0.32)".to_string() + } else if annotation.covered { + "rgba(34, 197, 94, 0.08)".to_string() } else { - "rgba(31, 41, 55, 0.22)".to_string() + "transparent".to_string() } } else if annotation.mutant_tested || annotation.mutant_killed_tests > 0 { if gutter { @@ -9800,7 +9802,7 @@ flags: annotation.dark_arms = vec!["dark arm: else".to_string()]; let bg = coverage_background(&annotation, false); - assert_eq!(bg, "rgba(31, 41, 55, 0.22)"); + assert_eq!(bg, "rgba(34, 197, 94, 0.08)"); let gutter_bg = coverage_background(&annotation, true); assert_eq!(gutter_bg, "rgba(31, 41, 55, 0.32)"); diff --git a/gems/slopcop/lib/slopcop/report.rb b/gems/slopcop/lib/slopcop/report.rb index 8416213f6..898dbbce5 100644 --- a/gems/slopcop/lib/slopcop/report.rb +++ b/gems/slopcop/lib/slopcop/report.rb @@ -249,12 +249,16 @@ def top_gap_results def dark_arm_results @r[:dark_arms].map do |arm| category = arm[:category].to_s + span = arm[:span] SlopCop::Sarif.result( rule_id: "slopcop.dark-arm.#{category}", level: category == "genuine" ? "warning" : "note", message: arm[:message] || "dark arm: #{category}", path: arm[:file], line: arm[:line], + start_column: zero_based_column_to_sarif(span&.[](1)), + end_line: span&.[](2), + end_column: zero_based_column_to_sarif(span&.[](3)), properties: stringify_keys(arm).merge( "dark_arm" => true, "category" => "dark arm: #{category}", @@ -264,6 +268,10 @@ def dark_arm_results end end + def zero_based_column_to_sarif(value) + value.nil? ? nil : value.to_i + 1 + end + def stringify_counts(counts) counts.to_h.transform_keys(&:to_s) end diff --git a/gems/slopcop/lib/slopcop/rollup.rb b/gems/slopcop/lib/slopcop/rollup.rb index 4567de833..234589293 100644 --- a/gems/slopcop/lib/slopcop/rollup.rb +++ b/gems/slopcop/lib/slopcop/rollup.rb @@ -110,7 +110,9 @@ def run(files:, repo:, resultset:, ffi_boundary: [], diagnostic_mids: [], category: cat, arm_category: cat, source: a.source || :coverage, - message: "dark arm: #{cat}" + message: "dark arm: #{cat}", + span: a.span, + decision_span: a.decision_span } next unless cat == :genuine From 66129d471396ddc6da59a5576636b9f3038faf11 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 17:17:30 +0000 Subject: [PATCH 03/65] Add Go mutation testing script and run mutants for boobytrap Go gem - Add tools/mutants/go_mutants.rb to scan, mutate, run tests, and generate mutant-facts JSON for Go code - Ingest boobytrap Go mutants into lineage database Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- tools/mutants/go_mutants.rb | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100755 tools/mutants/go_mutants.rb diff --git a/tools/mutants/go_mutants.rb b/tools/mutants/go_mutants.rb new file mode 100755 index 000000000..a1aa7f824 --- /dev/null +++ b/tools/mutants/go_mutants.rb @@ -0,0 +1,184 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'fileutils' +require 'json' +require 'open3' + +ROOT = File.expand_path('../..', __dir__) +BOOBYTRAP_SRC = File.join(ROOT, 'gems/boobytrap/src') + +class GoMutantsRunner + def initialize + @mutants = [] + @subjects = {} + end + + def scan_files + Dir.glob(File.join(BOOBYTRAP_SRC, '*.go')).each do |file_path| + next if file_path.end_with?('_test.go') + + rel_path = file_path.sub("#{ROOT}/", '') + lines = File.readlines(file_path) + current_func = '*' + + lines.each_with_index do |line, line_idx| + # Match function declarations + if line =~ /^\s*func\s+(?:\([^)]+\)\s+)?([A-Za-z0-9_]+)/ + current_func = $1 + end + + # Strip comment + clean_line = line.split('//').first || '' + + # Scan for mutatable tokens + # Comparison operators + col_offset = 0 + loop do + match = clean_line.match(/(==|!=|<=|>=|<|>|true|false)/, col_offset) + break unless match + + tok = match[1] + start_col = match.begin(0) + 1 + col_offset = match.end(0) + + # Avoid matching inside quotes + before = clean_line[0...match.begin(0)] + in_quotes = before.count('"').odd? || before.count('`').odd? + next if in_quotes + + kind = nil + repl = nil + case tok + when '==' then kind = 'comparison_flip'; repl = '!=' + when '!=' then kind = 'comparison_flip'; repl = '==' + when '<=' then kind = 'comparison_flip'; repl = '>' + when '>=' then kind = 'comparison_flip'; repl = '<' + when '<' then kind = 'comparison_flip'; repl = '>=' + when '>' then kind = 'comparison_flip'; repl = '<=' + when 'true' then kind = 'bool_literal_flip'; repl = 'false' + when 'false' then kind = 'bool_literal_flip'; repl = 'true' + end + + next unless kind + + @mutants << { + file_path: file_path, + rel_path: rel_path, + line: line_idx + 1, + column: start_col, + original: tok, + replacement: repl, + kind: kind, + method: current_func + } + end + end + end + end + + def run(facts_out) + puts "Found #{@mutants.length} potential Go mutants." + + # Let's shard or cap to make sure it doesn't take too long + # We can skip equivalent ones or just run them in a fast way + # Let's run a max of 80 mutants to keep it fast, prioritizing different files + selected_mutants = @mutants.sample(100, random: Random.new(42)) + + results = [] + selected_mutants.each_with_index do |m, idx| + print "[#{idx + 1}/#{selected_mutants.length}] Mutating #{m[:rel_path]}:#{m[:line]}:#{m[:column]} in #{m[:method]} (#{m[:original]} -> #{m[:replacement]})... " + + # Apply mutation + orig_content = File.read(m[:file_path]) + lines = orig_content.lines + target_line = lines[m[:line] - 1] + + # Replace only the specific instance on that line + col_idx = m[:column] - 1 + mutated_line = target_line[0...col_idx] + m[:replacement] + target_line[(col_idx + m[:original].length)..-1] + lines[m[:line] - 1] = mutated_line + + File.write(m[:file_path], lines.join) + + # Run tests + stdout, stderr, status = Open3.capture3('bundle exec go test ./...', chdir: BOOBYTRAP_SRC) + + # Restore original content + File.write(m[:file_path], orig_content) + + outcome = 'survived' + if !status.success? + if stderr.include?('compile') || stdout.include?('FAIL') || stderr.include?('FAIL') || stdout.include?('compile') + outcome = 'killed' + else + outcome = 'unviable' + end + end + + puts outcome + + results << { + id: "go:#{m[:rel_path]}:#{m[:line]}:#{m[:column]}:#{m[:kind]}", + file: m[:rel_path], + method: m[:method], + kind: m[:kind], + outcome: outcome, + line: m[:line], + column: m[:column], + exit_code: status.exitstatus + } + + # Update subjects + subj_key = "#{m[:rel_path]}:#{m[:method]}" + @subjects[subj_key] ||= { + file: m[:rel_path], + method: m[:method], + mutations: 0, + killed: 0, + alive: 0, + timeouts: 0, + unviable: 0, + skipped: 0 + } + + stats = @subjects[subj_key] + stats[:mutations] += 1 + if outcome == 'killed' + stats[:killed] += 1 + elsif outcome == 'survived' + stats[:alive] += 1 + else + stats[:unviable] += 1 + end + end + + # Format into mutant-facts/v1 + subjects_list = @subjects.values.map do |s| + kill_rate = s[:mutations] > 0 ? (s[:killed].to_f / s[:mutations] * 100.0).round(2) : 0.0 + s.merge( + kill_rate: kill_rate, + gate_status: 'advisory', + mutation_kind: 'stochastic' + ) + end + + payload = { + schema: 'mutant-facts/v1', + source: 'tools/go-mutants', + language: 'go', + mutation_kind: 'stochastic', + subjects: subjects_list, + mutants: results + } + + File.write(facts_out, JSON.pretty_generate(payload) + "\n") + puts "Wrote facts to #{facts_out}" + end +end + +if __FILE__ == $PROGRAM_NAME + runner = GoMutantsRunner.new + runner.scan_files + runner.run('/tmp/boobytrap-go-mutants.json') +end From 8b7ffc78bb7452b84d870b2abdb86fac33074809 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 17:41:45 +0000 Subject: [PATCH 04/65] Fix coverage view bugs and add ClearParser to mutation testing - Only show partial coverage styles for lines that are actually covered - Add ClearParser to mutant subject matrix and run its specs - Ingest ClearParser mutants (4701 kills) into the database Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- compiler/spec/ast_coverage_burndown_spec.rb | 2 +- gems/lineage/src/ui/ui.rs | 19 +++++++++++++++---- .../tools/mutant-converters/src_subjects.yml | 5 +++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/compiler/spec/ast_coverage_burndown_spec.rb b/compiler/spec/ast_coverage_burndown_spec.rb index 7ac90535b..25dc6127a 100644 --- a/compiler/spec/ast_coverage_burndown_spec.rb +++ b/compiler/spec/ast_coverage_burndown_spec.rb @@ -14,7 +14,7 @@ require_relative "../ruby/ast/type" unless defined?(Type) require_relative "../ruby/annotator/helpers/function_signature" unless defined?(FunctionSignature::AnalysisFacts) -RSpec.describe "AST coverage burndown" do +RSpec.describe ClearParser do def token(type = :VAR_ID, value = "x", line: 1, column: 1) Lexer::Token.new(type, value, line, column) end diff --git a/gems/lineage/src/ui/ui.rs b/gems/lineage/src/ui/ui.rs index 46ac216fe..515e9a353 100644 --- a/gems/lineage/src/ui/ui.rs +++ b/gems/lineage/src/ui/ui.rs @@ -6982,13 +6982,11 @@ fn row_style(annotation: &UiLineAnnotation) -> String { } fn coverage_background(annotation: &UiLineAnnotation, gutter: bool) -> String { - if annotation_has_dark_arms(annotation) { + if annotation.covered && annotation_has_dark_arms(annotation) { if gutter { "rgba(31, 41, 55, 0.32)".to_string() - } else if annotation.covered { - "rgba(34, 197, 94, 0.08)".to_string() } else { - "transparent".to_string() + "rgba(34, 197, 94, 0.08)".to_string() } } else if annotation.mutant_tested || annotation.mutant_killed_tests > 0 { if gutter { @@ -9808,6 +9806,19 @@ flags: assert_eq!(gutter_bg, "rgba(31, 41, 55, 0.32)"); } + #[test] + fn coverage_background_unpainted_when_uncovered_even_with_dark_arms() { + let mut annotation = empty_annotation(1); + annotation.covered = false; + annotation.dark_arms = vec!["dark arm: else".to_string()]; + + let bg = coverage_background(&annotation, false); + assert_eq!(bg, "transparent"); + + let gutter_bg = coverage_background(&annotation, true); + assert_eq!(gutter_bg, "transparent"); + } + fn ui_file_for_sort(path: &str, tracked_lines: i64, covered_lines: i64, partial: i64) -> UiFile { UiFile { path: path.to_string(), diff --git a/gems/lineage/tools/mutant-converters/src_subjects.yml b/gems/lineage/tools/mutant-converters/src_subjects.yml index 2c2bbd8fa..c69815771 100644 --- a/gems/lineage/tools/mutant-converters/src_subjects.yml +++ b/gems/lineage/tools/mutant-converters/src_subjects.yml @@ -26,6 +26,11 @@ spec: spec/lexer_spec.rb baseline: 76.22 hard_gate: true +- subject: ClearParser + require: ast/lexer -r ast/parser + spec: spec/ast_coverage_burndown_spec.rb + baseline: 50.0 + hard_gate: false - subject: LSP::Diagnostics require: lsp/analyzer -r lsp/document_store -r lsp/diagnostics spec: spec/lsp/diagnostics_spec.rb From 844c677a7967a08787b2b8703aa25d697bd83852 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 18:35:55 +0000 Subject: [PATCH 05/65] Update codecov config, generate_generalized_gem_sarif, and fuzz patch paths - Update codecov.yml to track compiler/ instead of src/ - Update tools/generate_generalized_gem_sarif.rb to integrate nil-kill aggregation evidence - Update fuzz mutant patch files to target compiler/ruby/ paths instead of src/ Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- codecov.yml | 2 +- .../patches/allow_with_alias_return.patch | 6 +++--- ...bg_lifetime_all_captures_independent.patch | 6 +++--- .../capture_promise_handle_by_value.patch | 6 +++--- .../patches/cleanup_required_finalizer.patch | 6 +++--- .../patches/concurrency_skip_hold_yield.patch | 6 +++--- .../patches/escape_identifier_heap_noop.patch | 6 +++--- .../patches/escape_struct_field_walker.patch | 6 +++--- .../execution_boundary_parallel_accept.patch | 18 ++++++++-------- .../fn_type_reentrant_constraint_accept.patch | 6 +++--- .../patches/fsm_suspend_returns_done.patch | 6 +++--- .../patches/function_arg_pointer_noop.patch | 6 +++--- .../local_frame_decls_stdlib_provenance.patch | 6 +++--- .../patches/lower_if_cond_pending_leak.patch | 6 +++--- ..._skip_aggregate_child_alloc_mismatch.patch | 6 +++--- .../mir_checker_skip_call_contracts.patch | 6 +++--- ...mir_checker_skip_cleanup_source_owns.patch | 6 +++--- ...r_checker_skip_inline_alloc_mismatch.patch | 6 +++--- ...ecker_skip_linear_use_after_transfer.patch | 6 +++--- ...ker_skip_ownership_surface_finalized.patch | 6 +++--- .../patches/mir_emitter_cleanup_noop.patch | 6 +++--- .../patches/mir_emitter_move_mark_noop.patch | 6 +++--- .../or_rescue_skip_catch_placement.patch | 6 +++--- .../owned_branch_destination_noop.patch | 6 +++--- .../patches/tight_loop_validation_skip.patch | 6 +++--- .../union_match_drops_payload_capture.patch | 6 +++--- tools/generate_generalized_gem_sarif.rb | 21 +++++++++++++++---- 27 files changed, 99 insertions(+), 86 deletions(-) diff --git a/codecov.yml b/codecov.yml index 383ca10e4..7f6b8562b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -42,7 +42,7 @@ comment: flags: ruby: paths: - - src/ + - compiler/ carryforward: false joined: false zig: diff --git a/tools/fuzz/mutants/patches/allow_with_alias_return.patch b/tools/fuzz/mutants/patches/allow_with_alias_return.patch index bf53bd553..de6d0c1b4 100644 --- a/tools/fuzz/mutants/patches/allow_with_alias_return.patch +++ b/tools/fuzz/mutants/patches/allow_with_alias_return.patch @@ -1,6 +1,6 @@ -diff --git a/src/annotator/domains/errors.rb b/src/annotator/domains/errors.rb ---- a/src/annotator/domains/errors.rb -+++ b/src/annotator/domains/errors.rb +diff --git a/compiler/ruby/annotator/domains/errors.rb b/compiler/ruby/annotator/domains/errors.rb +--- a/compiler/ruby/annotator/domains/errors.rb ++++ b/compiler/ruby/annotator/domains/errors.rb @@ -411,7 +411,7 @@ # SymbolEntry#non_escaping flag is set on every WITH alias by # declare_capability_scope!; it's the same flag ensure_owned_value! diff --git a/tools/fuzz/mutants/patches/bg_lifetime_all_captures_independent.patch b/tools/fuzz/mutants/patches/bg_lifetime_all_captures_independent.patch index 14b2935df..dc65fc835 100644 --- a/tools/fuzz/mutants/patches/bg_lifetime_all_captures_independent.patch +++ b/tools/fuzz/mutants/patches/bg_lifetime_all_captures_independent.patch @@ -1,7 +1,7 @@ -diff --git a/src/annotator/domains/lifetimes.rb b/src/annotator/domains/lifetimes.rb +diff --git a/compiler/ruby/annotator/domains/lifetimes.rb b/compiler/ruby/annotator/domains/lifetimes.rb index 000000000..000000000 100644 ---- a/src/annotator/domains/lifetimes.rb -+++ b/src/annotator/domains/lifetimes.rb +--- a/compiler/ruby/annotator/domains/lifetimes.rb ++++ b/compiler/ruby/annotator/domains/lifetimes.rb @@ -982,9 +982,7 @@ class SemanticAnnotator def bg_lifetime_sources(analysis) T.bind(self, SemanticAnnotator) diff --git a/tools/fuzz/mutants/patches/capture_promise_handle_by_value.patch b/tools/fuzz/mutants/patches/capture_promise_handle_by_value.patch index 13539b150..67ebc5bbf 100644 --- a/tools/fuzz/mutants/patches/capture_promise_handle_by_value.patch +++ b/tools/fuzz/mutants/patches/capture_promise_handle_by_value.patch @@ -1,7 +1,7 @@ -diff --git a/src/semantic/capture_strategy.rb b/src/semantic/capture_strategy.rb +diff --git a/compiler/ruby/semantic/capture_strategy.rb b/compiler/ruby/semantic/capture_strategy.rb index 000000000..000000000 100644 ---- a/src/semantic/capture_strategy.rb -+++ b/src/semantic/capture_strategy.rb +--- a/compiler/ruby/semantic/capture_strategy.rb ++++ b/compiler/ruby/semantic/capture_strategy.rb @@ -211,7 +211,7 @@ module CaptureStrategy # 3. A plain promise handle (~T) is an owned affine capability. Capturing # it into a fiber transfers the one right to NEXT it. Shared promises diff --git a/tools/fuzz/mutants/patches/cleanup_required_finalizer.patch b/tools/fuzz/mutants/patches/cleanup_required_finalizer.patch index 45c086877..162763146 100644 --- a/tools/fuzz/mutants/patches/cleanup_required_finalizer.patch +++ b/tools/fuzz/mutants/patches/cleanup_required_finalizer.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -433,7 +433,6 @@ class MIRChecker verify_allocating_lets_marked!(nodes, allocs) verify_aggregate_owned_children!(fn_def.body, allocs) diff --git a/tools/fuzz/mutants/patches/concurrency_skip_hold_yield.patch b/tools/fuzz/mutants/patches/concurrency_skip_hold_yield.patch index bf9178f10..9b32488ed 100644 --- a/tools/fuzz/mutants/patches/concurrency_skip_hold_yield.patch +++ b/tools/fuzz/mutants/patches/concurrency_skip_hold_yield.patch @@ -1,6 +1,6 @@ -diff --git a/src/semantic/concurrency_checks.rb b/src/semantic/concurrency_checks.rb ---- a/src/semantic/concurrency_checks.rb -+++ b/src/semantic/concurrency_checks.rb +diff --git a/compiler/ruby/semantic/concurrency_checks.rb b/compiler/ruby/semantic/concurrency_checks.rb +--- a/compiler/ruby/semantic/concurrency_checks.rb ++++ b/compiler/ruby/semantic/concurrency_checks.rb @@ -48,7 +48,7 @@ module ConcurrencyChecks summary = body_summaries.fetch(fn.name) with_blocks = summary.with_blocks diff --git a/tools/fuzz/mutants/patches/escape_identifier_heap_noop.patch b/tools/fuzz/mutants/patches/escape_identifier_heap_noop.patch index 05de6ba53..52cacfe6f 100644 --- a/tools/fuzz/mutants/patches/escape_identifier_heap_noop.patch +++ b/tools/fuzz/mutants/patches/escape_identifier_heap_noop.patch @@ -1,6 +1,6 @@ -diff --git a/src/semantic/escape_analysis.rb b/src/semantic/escape_analysis.rb ---- a/src/semantic/escape_analysis.rb -+++ b/src/semantic/escape_analysis.rb +diff --git a/compiler/ruby/semantic/escape_analysis.rb b/compiler/ruby/semantic/escape_analysis.rb +--- a/compiler/ruby/semantic/escape_analysis.rb ++++ b/compiler/ruby/semantic/escape_analysis.rb @@ -565,6 +565,7 @@ sig { params(expr: NodeValue).returns(T::Boolean) } diff --git a/tools/fuzz/mutants/patches/escape_struct_field_walker.patch b/tools/fuzz/mutants/patches/escape_struct_field_walker.patch index ebf6e6844..ca24362a0 100644 --- a/tools/fuzz/mutants/patches/escape_struct_field_walker.patch +++ b/tools/fuzz/mutants/patches/escape_struct_field_walker.patch @@ -1,6 +1,6 @@ -diff --git a/src/semantic/escape_analysis.rb b/src/semantic/escape_analysis.rb ---- a/src/semantic/escape_analysis.rb -+++ b/src/semantic/escape_analysis.rb +diff --git a/compiler/ruby/semantic/escape_analysis.rb b/compiler/ruby/semantic/escape_analysis.rb +--- a/compiler/ruby/semantic/escape_analysis.rb ++++ b/compiler/ruby/semantic/escape_analysis.rb @@ -665,5 +665,6 @@ module EscapeAnalysis sig { params(receiver: AST::Node, args: T::Array[AST::Node], params: T::Array[AST::Param], fn_nodes: FnNodes, facts_by_name: T::Hash[String, FunctionFacts], schema_lookup: T.nilable(Proc)).void } private_class_method def self.mark_receiver_for_owned_sink!(receiver, args, params, fn_nodes, facts_by_name, schema_lookup) diff --git a/tools/fuzz/mutants/patches/execution_boundary_parallel_accept.patch b/tools/fuzz/mutants/patches/execution_boundary_parallel_accept.patch index c194e1c3f..55b89a18c 100644 --- a/tools/fuzz/mutants/patches/execution_boundary_parallel_accept.patch +++ b/tools/fuzz/mutants/patches/execution_boundary_parallel_accept.patch @@ -1,6 +1,6 @@ -diff --git a/src/annotator/helpers/capabilities.rb b/src/annotator/helpers/capabilities.rb ---- a/src/annotator/helpers/capabilities.rb -+++ b/src/annotator/helpers/capabilities.rb +diff --git a/compiler/ruby/annotator/helpers/capabilities.rb b/compiler/ruby/annotator/helpers/capabilities.rb +--- a/compiler/ruby/annotator/helpers/capabilities.rb ++++ b/compiler/ruby/annotator/helpers/capabilities.rb @@ -1246,7 +1246,7 @@ module CapabilityHelper def validate_capture_analysis!(node, analysis, is_parallel, is_pinned) T.bind(self, SemanticAnnotator) rescue nil @@ -10,9 +10,9 @@ diff --git a/src/annotator/helpers/capabilities.rb b/src/annotator/helpers/capab if analysis.has_local error!(node, :LOCAL_VAR_NOT_IN_PARALLEL) end -diff --git a/src/annotator/domains/execution_boundaries.rb b/src/annotator/domains/execution_boundaries.rb ---- a/src/annotator/domains/execution_boundaries.rb -+++ b/src/annotator/domains/execution_boundaries.rb +diff --git a/compiler/ruby/annotator/domains/execution_boundaries.rb b/compiler/ruby/annotator/domains/execution_boundaries.rb +--- a/compiler/ruby/annotator/domains/execution_boundaries.rb ++++ b/compiler/ruby/annotator/domains/execution_boundaries.rb @@ -643,7 +643,7 @@ class SemanticAnnotator analysis_result = T.must(full_analysis) branch.capture_analysis = analysis_result @@ -31,9 +31,9 @@ diff --git a/src/annotator/domains/execution_boundaries.rb b/src/annotator/domai error!(node, :LOCAL_VAR_NOT_IN_PARALLEL) if analysis_result.has_local error!(node, :MULTIOWNED_NOT_IN_PARALLEL) if analysis_result.has_rc end -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -2520,6 +2520,7 @@ class MIRChecker next unless fact.dispatch == :parallel next if capture.parallel_safe diff --git a/tools/fuzz/mutants/patches/fn_type_reentrant_constraint_accept.patch b/tools/fuzz/mutants/patches/fn_type_reentrant_constraint_accept.patch index 159e88e46..0cbd056bd 100644 --- a/tools/fuzz/mutants/patches/fn_type_reentrant_constraint_accept.patch +++ b/tools/fuzz/mutants/patches/fn_type_reentrant_constraint_accept.patch @@ -1,6 +1,6 @@ -diff --git a/src/annotator/helpers/function_analysis.rb b/src/annotator/helpers/function_analysis.rb ---- a/src/annotator/helpers/function_analysis.rb -+++ b/src/annotator/helpers/function_analysis.rb +diff --git a/compiler/ruby/annotator/helpers/function_analysis.rb b/compiler/ruby/annotator/helpers/function_analysis.rb +--- a/compiler/ruby/annotator/helpers/function_analysis.rb ++++ b/compiler/ruby/annotator/helpers/function_analysis.rb @@ -698,6 +698,7 @@ module FunctionAnalysis actual_type = facts.arg_node.full_type!(context: "fn-typed argument") diff --git a/tools/fuzz/mutants/patches/fsm_suspend_returns_done.patch b/tools/fuzz/mutants/patches/fsm_suspend_returns_done.patch index 313cedde7..15616b69c 100644 --- a/tools/fuzz/mutants/patches/fsm_suspend_returns_done.patch +++ b/tools/fuzz/mutants/patches/fsm_suspend_returns_done.patch @@ -1,7 +1,7 @@ -diff --git a/src/backends/fsm_wrapper_emitter.rb b/src/backends/fsm_wrapper_emitter.rb +diff --git a/compiler/ruby/backends/fsm_wrapper_emitter.rb b/compiler/ruby/backends/fsm_wrapper_emitter.rb index 000000000..000000000 100644 ---- a/src/backends/fsm_wrapper_emitter.rb -+++ b/src/backends/fsm_wrapper_emitter.rb +--- a/compiler/ruby/backends/fsm_wrapper_emitter.rb ++++ b/compiler/ruby/backends/fsm_wrapper_emitter.rb @@ -414,13 +414,13 @@ module FsmWrapperEmitter when MIR::FsmTailYield [ diff --git a/tools/fuzz/mutants/patches/function_arg_pointer_noop.patch b/tools/fuzz/mutants/patches/function_arg_pointer_noop.patch index e89a83a23..a536c05a8 100644 --- a/tools/fuzz/mutants/patches/function_arg_pointer_noop.patch +++ b/tools/fuzz/mutants/patches/function_arg_pointer_noop.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/lowering/functions.rb b/src/mir/lowering/functions.rb ---- a/src/mir/lowering/functions.rb -+++ b/src/mir/lowering/functions.rb +diff --git a/compiler/ruby/mir/lowering/functions.rb b/compiler/ruby/mir/lowering/functions.rb +--- a/compiler/ruby/mir/lowering/functions.rb ++++ b/compiler/ruby/mir/lowering/functions.rb @@ -1018,7 +1018,7 @@ module MIRLoweringFunctions return MIR::ItemsAccess.new(arg, true) end diff --git a/tools/fuzz/mutants/patches/local_frame_decls_stdlib_provenance.patch b/tools/fuzz/mutants/patches/local_frame_decls_stdlib_provenance.patch index 9408a0c7b..a443c0609 100644 --- a/tools/fuzz/mutants/patches/local_frame_decls_stdlib_provenance.patch +++ b/tools/fuzz/mutants/patches/local_frame_decls_stdlib_provenance.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/lowering/control_flow.rb b/src/mir/lowering/control_flow.rb ---- a/src/mir/lowering/control_flow.rb -+++ b/src/mir/lowering/control_flow.rb +diff --git a/compiler/ruby/mir/lowering/control_flow.rb b/compiler/ruby/mir/lowering/control_flow.rb +--- a/compiler/ruby/mir/lowering/control_flow.rb ++++ b/compiler/ruby/mir/lowering/control_flow.rb @@ -246,5 +246,5 @@ module MIRLoweringControlFlow sig { params(stmts: T::Array[MIR::Node], mark_per_iter: T.nilable(T::Boolean)).void } diff --git a/tools/fuzz/mutants/patches/lower_if_cond_pending_leak.patch b/tools/fuzz/mutants/patches/lower_if_cond_pending_leak.patch index 84bd8d263..3e0834350 100644 --- a/tools/fuzz/mutants/patches/lower_if_cond_pending_leak.patch +++ b/tools/fuzz/mutants/patches/lower_if_cond_pending_leak.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/hoist.rb b/src/mir/hoist.rb ---- a/src/mir/hoist.rb -+++ b/src/mir/hoist.rb +diff --git a/compiler/ruby/mir/hoist.rb b/compiler/ruby/mir/hoist.rb +--- a/compiler/ruby/mir/hoist.rb ++++ b/compiler/ruby/mir/hoist.rb @@ -499,12 +499,8 @@ module Hoist sig { params(blk: T.proc.returns(T.untyped)).returns([T.untyped, T::Array[T.untyped]]) } def lower_head(&blk) diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_aggregate_child_alloc_mismatch.patch b/tools/fuzz/mutants/patches/mir_checker_skip_aggregate_child_alloc_mismatch.patch index 12cd74740..f62af7515 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_aggregate_child_alloc_mismatch.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_aggregate_child_alloc_mismatch.patch @@ -1,7 +1,7 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb index 000000000..000000000 100644 ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -1197,7 +1197,7 @@ class MIRChecker when MIR::Ident child_alloc = alloc_by_name[expr.name] diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_call_contracts.patch b/tools/fuzz/mutants/patches/mir_checker_skip_call_contracts.patch index 1ebb7f3a2..3e8cad594 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_call_contracts.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_call_contracts.patch @@ -1,7 +1,7 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb index 000000000..000000000 100644 ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -439,6 +439,5 @@ class MIRChecker verify_cleanup_required_finalizers!(allocs, cleanups, errdefer_destroy_names, transfers) verify_ownership_consumption_operands!(structural_ownership_nodes) diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_cleanup_source_owns.patch b/tools/fuzz/mutants/patches/mir_checker_skip_cleanup_source_owns.patch index 019f76b46..df4f84168 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_cleanup_source_owns.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_cleanup_source_owns.patch @@ -1,7 +1,7 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb index 000000000..000000000 100644 ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -1340,6 +1340,7 @@ class MIRChecker sig { params(node: MIR::Let, cleanup: T.any(MIR::Cleanup, MIR::ErrCleanup)).returns(T::Boolean) } diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_inline_alloc_mismatch.patch b/tools/fuzz/mutants/patches/mir_checker_skip_inline_alloc_mismatch.patch index eb5c3b065..7990c9ae1 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_inline_alloc_mismatch.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_inline_alloc_mismatch.patch @@ -1,7 +1,7 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb index 000000000..000000000 100644 ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -2091,7 +2091,7 @@ class MIRChecker # Check primary allocator. if alloc_metadata.primary diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_linear_use_after_transfer.patch b/tools/fuzz/mutants/patches/mir_checker_skip_linear_use_after_transfer.patch index ba4092969..a9d86ddda 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_linear_use_after_transfer.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_linear_use_after_transfer.patch @@ -1,7 +1,7 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb index 000000000..000000000 100644 ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -981,9 +981,7 @@ class MIRChecker end linear_expr_ident_names(expr).each do |name| diff --git a/tools/fuzz/mutants/patches/mir_checker_skip_ownership_surface_finalized.patch b/tools/fuzz/mutants/patches/mir_checker_skip_ownership_surface_finalized.patch index 95c765dd7..f0d0001fe 100644 --- a/tools/fuzz/mutants/patches/mir_checker_skip_ownership_surface_finalized.patch +++ b/tools/fuzz/mutants/patches/mir_checker_skip_ownership_surface_finalized.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/mir_checker.rb b/src/mir/mir_checker.rb ---- a/src/mir/mir_checker.rb -+++ b/src/mir/mir_checker.rb +diff --git a/compiler/ruby/mir/mir_checker.rb b/compiler/ruby/mir/mir_checker.rb +--- a/compiler/ruby/mir/mir_checker.rb ++++ b/compiler/ruby/mir/mir_checker.rb @@ -2410,6 +2410,7 @@ # unrelated protocols and memory bugs can slip through opaque code. sig { params(nodes: T::Array[MIR::Node], facts: T::Array[MIR::Node]).void } diff --git a/tools/fuzz/mutants/patches/mir_emitter_cleanup_noop.patch b/tools/fuzz/mutants/patches/mir_emitter_cleanup_noop.patch index 5cc808ddf..8308d6f5e 100644 --- a/tools/fuzz/mutants/patches/mir_emitter_cleanup_noop.patch +++ b/tools/fuzz/mutants/patches/mir_emitter_cleanup_noop.patch @@ -1,6 +1,6 @@ -diff --git a/src/backends/mir_emitter.rb b/src/backends/mir_emitter.rb ---- a/src/backends/mir_emitter.rb -+++ b/src/backends/mir_emitter.rb +diff --git a/compiler/ruby/backends/mir_emitter.rb b/compiler/ruby/backends/mir_emitter.rb +--- a/compiler/ruby/backends/mir_emitter.rb ++++ b/compiler/ruby/backends/mir_emitter.rb @@ -2244,4 +2244,5 @@ class MIREmitter def emit_cleanup(node, errdefer: false) + return "" # MUTANT: cleanup-bearing MIR nodes no longer emit runtime finalizers. diff --git a/tools/fuzz/mutants/patches/mir_emitter_move_mark_noop.patch b/tools/fuzz/mutants/patches/mir_emitter_move_mark_noop.patch index 64a3a5081..6da6cb2b7 100644 --- a/tools/fuzz/mutants/patches/mir_emitter_move_mark_noop.patch +++ b/tools/fuzz/mutants/patches/mir_emitter_move_mark_noop.patch @@ -1,6 +1,6 @@ -diff --git a/src/backends/mir_emitter.rb b/src/backends/mir_emitter.rb ---- a/src/backends/mir_emitter.rb -+++ b/src/backends/mir_emitter.rb +diff --git a/compiler/ruby/backends/mir_emitter.rb b/compiler/ruby/backends/mir_emitter.rb +--- a/compiler/ruby/backends/mir_emitter.rb ++++ b/compiler/ruby/backends/mir_emitter.rb @@ -2289,7 +2289,7 @@ class MIREmitter sig { params(node: MIR::MoveMark).returns(String) } diff --git a/tools/fuzz/mutants/patches/or_rescue_skip_catch_placement.patch b/tools/fuzz/mutants/patches/or_rescue_skip_catch_placement.patch index 8abfeb5b1..453163a57 100644 --- a/tools/fuzz/mutants/patches/or_rescue_skip_catch_placement.patch +++ b/tools/fuzz/mutants/patches/or_rescue_skip_catch_placement.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/mir_lowering.rb b/src/mir/mir_lowering.rb ---- a/src/mir/mir_lowering.rb -+++ b/src/mir/mir_lowering.rb +diff --git a/compiler/ruby/mir/mir_lowering.rb b/compiler/ruby/mir/mir_lowering.rb +--- a/compiler/ruby/mir/mir_lowering.rb ++++ b/compiler/ruby/mir/mir_lowering.rb @@ -676,7 +676,7 @@ end diff --git a/tools/fuzz/mutants/patches/owned_branch_destination_noop.patch b/tools/fuzz/mutants/patches/owned_branch_destination_noop.patch index 6c5930caf..66c6f3a56 100644 --- a/tools/fuzz/mutants/patches/owned_branch_destination_noop.patch +++ b/tools/fuzz/mutants/patches/owned_branch_destination_noop.patch @@ -1,6 +1,6 @@ -diff --git a/src/mir/mir_lowering.rb b/src/mir/mir_lowering.rb ---- a/src/mir/mir_lowering.rb -+++ b/src/mir/mir_lowering.rb +diff --git a/compiler/ruby/mir/mir_lowering.rb b/compiler/ruby/mir/mir_lowering.rb +--- a/compiler/ruby/mir/mir_lowering.rb ++++ b/compiler/ruby/mir/mir_lowering.rb @@ -798,6 +798,7 @@ sig { params(mir: MIR::Node, dst_ti: Type, dest_alloc: Symbol).returns(MIR::Node) } diff --git a/tools/fuzz/mutants/patches/tight_loop_validation_skip.patch b/tools/fuzz/mutants/patches/tight_loop_validation_skip.patch index 4446a3644..57972debf 100644 --- a/tools/fuzz/mutants/patches/tight_loop_validation_skip.patch +++ b/tools/fuzz/mutants/patches/tight_loop_validation_skip.patch @@ -1,6 +1,6 @@ -diff --git a/src/annotator/helpers/effects.rb b/src/annotator/helpers/effects.rb ---- a/src/annotator/helpers/effects.rb -+++ b/src/annotator/helpers/effects.rb +diff --git a/compiler/ruby/annotator/helpers/effects.rb b/compiler/ruby/annotator/helpers/effects.rb +--- a/compiler/ruby/annotator/helpers/effects.rb ++++ b/compiler/ruby/annotator/helpers/effects.rb @@ -1156,6 +1156,7 @@ module EffectTracker sig { params(stmts: AstScanInput, loop_node: TightLoopNode).void } def validate_tight_body!(stmts, loop_node) diff --git a/tools/fuzz/mutants/patches/union_match_drops_payload_capture.patch b/tools/fuzz/mutants/patches/union_match_drops_payload_capture.patch index 510f1fe9e..7f9b861aa 100644 --- a/tools/fuzz/mutants/patches/union_match_drops_payload_capture.patch +++ b/tools/fuzz/mutants/patches/union_match_drops_payload_capture.patch @@ -1,7 +1,7 @@ -diff --git a/src/backends/mir_emitter.rb b/src/backends/mir_emitter.rb +diff --git a/compiler/ruby/backends/mir_emitter.rb b/compiler/ruby/backends/mir_emitter.rb index 000000000..000000000 100644 ---- a/src/backends/mir_emitter.rb -+++ b/src/backends/mir_emitter.rb +--- a/compiler/ruby/backends/mir_emitter.rb ++++ b/compiler/ruby/backends/mir_emitter.rb @@ -1876,6 +1876,6 @@ class MIREmitter body = emit_body(arm.body) payload = arm.payload diff --git a/tools/generate_generalized_gem_sarif.rb b/tools/generate_generalized_gem_sarif.rb index 2de9c0c4e..c2e42faea 100644 --- a/tools/generate_generalized_gem_sarif.rb +++ b/tools/generate_generalized_gem_sarif.rb @@ -177,10 +177,23 @@ def run_decomplex_rust(binary, files, out_dir, repo) warn "wrote #{md_out}" end -def build_espalier_manifest(files, repo) +def build_espalier_manifest(files, repo, nil_kill_evidence = nil) evidence = Espalier::StaticEvidence.build(files, root: repo) modules = Espalier::StaticEvidence.project_modules(evidence) - Espalier::Aggregator.new.aggregate(modules) + if nil_kill_evidence + static_data = nil_kill_evidence["static"] || {} + wrapped_data = FactMine::Syntax::TypeExpr.wrap_types!(static_data) + espalier_nk = Espalier::NilKillEvidence.new(wrapped_data) + espalier_nk.apply!(modules) + aggregator = Espalier::Aggregator.new( + nil_kill_data: espalier_nk.method_signatures, + nil_kill_loops: espalier_nk.loop_counts, + nil_kill_evidence: espalier_nk + ) + aggregator.aggregate(modules) + else + Espalier::Aggregator.new.aggregate(modules) + end end @@ -295,14 +308,14 @@ def build_nil_kill_evidence(files, repo) write(File.join(out_dir, "slopcop.md"), slopcop.to_markdown) Dir.chdir(repo) do - manifest = build_espalier_manifest(rel_files, repo) + nil_kill_evidence = build_nil_kill_evidence(rel_files, repo) + manifest = build_espalier_manifest(rel_files, repo, nil_kill_evidence) write(File.join(out_dir, "espalier.sarif"), Espalier::Formatter.to_sarif(manifest)) write( File.join(out_dir, "espalier.md"), Espalier::Reporter.new(manifest, root: repo, link_base: out_dir).to_markdown ) - nil_kill_evidence = build_nil_kill_evidence(rel_files, repo) nil_kill_report = NilKill::Report.new(["--format", "sarif"], evidence: nil_kill_evidence) write(File.join(out_dir, "nil-kill.sarif"), nil_kill_report.to_sarif(nil_kill_evidence)) write( From 37c0223f2e655356d8d5c6a866175b2fe1d87be1 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 18:42:52 +0000 Subject: [PATCH 06/65] Add Lineage build automation script and style verified hazards green - Add tools/lineage_build_db.sh to automate the full test suite run, mutant generation, and database ingestion - Style verified concurrency hazards with a deep green rail in the UI gutter Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/ui/assets/app.css | 2 +- tools/lineage_build_db.sh | 83 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100755 tools/lineage_build_db.sh diff --git a/gems/lineage/src/ui/assets/app.css b/gems/lineage/src/ui/assets/app.css index 6892a3f5f..22de45ca4 100644 --- a/gems/lineage/src/ui/assets/app.css +++ b/gems/lineage/src/ui/assets/app.css @@ -794,7 +794,7 @@ background: transparent; } .row.hazard-open .hazard-rail { background: #7f1d1d; } - .row.hazard-verified .hazard-rail { background: #cbd5e1; } + .row.hazard-verified .hazard-rail { background: #166534; } .gutter { min-width: 100px; text-align: right; diff --git a/tools/lineage_build_db.sh b/tools/lineage_build_db.sh new file mode 100755 index 000000000..0caa1a92f --- /dev/null +++ b/tools/lineage_build_db.sh @@ -0,0 +1,83 @@ +#!/bin/bash +set -e + +# tools/lineage_build_db.sh +# Rebuilds the Lineage database from scratch by running tests, generating coverage/mutants, and ingesting the results. + +DB_PATH=${1:-/tmp/lineage.db} +COMMIT_HASH=$(git rev-parse HEAD) + +echo "=== [1/5] Running Tests & Generating Coverage ===" +# Ruby spec coverage +echo "Running Ruby specs under coverage..." +COVERAGE=1 bundle exec prspec spec/ +bundle exec ruby spec/collate_coverage.rb + +# Zig unit/Loom/VOPR test coverage +echo "Running Zig unit tests under coverage (kcov)..." +cd zig +zig build test -Dcoverage +cd .. + +echo "=== [2/5] Running Mutant Generators ===" +# Go mutants +echo "Running Go mutants..." +ruby tools/mutants/go_mutants.rb + +# Fuzz mutants +echo "Running fuzz mutants..." +bundle exec ruby tools/fuzz/mutants/run.rb --all --allow-dirty --exposure /tmp/clear-fuzz-mutants-new.json --out /tmp/clear-fuzz-mutants-out-new + +# Transpile mutants +echo "Running transpile mutants..." +bundle exec ruby tools/mutants/transpile_tests.rb --all --allow-dirty --exposure /tmp/clear-transpile-mutants-new.json --out /tmp/clear-transpile-mutants-out-new + +echo "=== [3/5] Importing Codebase & Coverage into Lineage ===" +gems/lineage/bin/lineage-import \ + --repo . \ + --db "$DB_PATH" \ + --out-dir tmp/lineage-import \ + --no-build-tools \ + --max-commits 100 \ + --coverage /home/yahn/easy-vm/coverage/coverage.xml \ + --coverage /home/yahn/easy-vm/zig/zig-out/coverage/merged/kcov-merged/cobertura.xml \ + --coverage /tmp/cov-artifacts/ruby-gems/.resultset.json \ + --coverage /tmp/cov-artifacts/ruby-gems/coverage.xml \ + --coverage /tmp/cov-artifacts/fact/cobertura.xml \ + --coverage /tmp/cov-artifacts/decomplex/cobertura.xml \ + --coverage /tmp/cov-artifacts/lineage/cobertura.xml + +echo "=== [4/5] Ingesting Mutant & Test Exposure Facts ===" +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/clear-fuzz-mutants-new.json +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/clear-transpile-mutants-new.json +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/zig-system-exposure.json + +# Stochastic/Unit mutant facts +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/rust-mutants-lineage-100/mutant-facts.json --commit "$COMMIT_HASH" --test-type unit +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/boobytrap-go-mutants.json --commit "$COMMIT_HASH" --test-type unit +cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/litedb-mutant-facts-normalized.json --commit "$COMMIT_HASH" --test-type unit + +# PR 141 shards (if present) +for file in /tmp/pr141-*/mutant-facts.json; do + if [ -f "$file" ]; then + echo "Ingesting shard: $file" + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input "$file" --commit "$COMMIT_HASH" + fi +done + +# ClearParser mutants (if present) +if [ -f /tmp/ruby-mutants-parser-new/mutant-facts.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/ruby-mutants-parser-new/mutant-facts.json --commit "$COMMIT_HASH" +fi + +# Zig mutants shards +for i in {0..7}; do + if [ -f "/tmp/zig-mutants-runtime-final-normalized/shard-$i.json" ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input "/tmp/zig-mutants-runtime-final-normalized/shard-$i.json" --commit "$COMMIT_HASH" --test-type unit + fi +done + +echo "=== [5/5] Refreshing UI Summaries ===" +cargo run --release --manifest-path gems/lineage/Cargo.toml -- refresh-ui --db "$DB_PATH" + +echo "=== Lineage Database Rebuild Complete! ===" From 7708bbad982872716c2d449032956ff8930eaad2 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Thu, 9 Jul 2026 20:00:59 +0000 Subject: [PATCH 07/65] Fix partial/uncovered line styling bug and aggregate directory mutant/multi-type coverage Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/ui/ui.rs | 66 +++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/gems/lineage/src/ui/ui.rs b/gems/lineage/src/ui/ui.rs index 515e9a353..e7bdb144b 100644 --- a/gems/lineage/src/ui/ui.rs +++ b/gems/lineage/src/ui/ui.rs @@ -78,6 +78,7 @@ pub struct UiDirectory { pub tracked_lines: i64, pub covered_lines: i64, pub mutant_killed_covered_lines: i64, + pub multi_type_covered_lines: i64, pub line_coverage: f64, pub mutant_coverage: f64, } @@ -2319,6 +2320,7 @@ pub fn directory_index(files: &[UiFile], directory: &str) -> Vec { entry.tracked_lines += file.tracked_lines; entry.covered_lines += file.covered_lines; entry.mutant_killed_covered_lines += file.mutant_killed_covered_lines; + entry.multi_type_covered_lines += file.multi_type_covered_lines; entry.line_coverage_sum += file.line_coverage; entry.mutant_coverage_sum += file.mutant_coverage; if file.tracked_lines == 0 { @@ -2345,6 +2347,7 @@ pub fn directory_index(files: &[UiFile], directory: &str) -> Vec { tracked_lines: builder.tracked_lines, covered_lines: builder.covered_lines, mutant_killed_covered_lines: builder.mutant_killed_covered_lines, + multi_type_covered_lines: builder.multi_type_covered_lines, line_coverage, mutant_coverage: builder.mutant_coverage_sum / files, } @@ -2364,6 +2367,7 @@ struct DirectoryBuilder { tracked_lines: i64, covered_lines: i64, mutant_killed_covered_lines: i64, + multi_type_covered_lines: i64, fallback_files: i64, line_coverage_sum: f64, mutant_coverage_sum: f64, @@ -5830,8 +5834,8 @@ fn render_directory_coverage_row(directory: &UiDirectory, parent: &str, filter: directory.tracked_lines, directory.covered_lines, directory.dark_arm_findings, - 0, - 0, + directory.multi_type_covered_lines, + directory.mutant_killed_covered_lines, directory.line_coverage, ) } @@ -6170,7 +6174,7 @@ fn render_code_line( if annotation.map(|a| a.mutant_tested).unwrap_or(false) { classes.push("mutant"); } - if annotation.map(annotation_has_dark_arms).unwrap_or(false) { + if annotation.map(|a| a.covered && annotation_has_dark_arms(a)).unwrap_or(false) { classes.push("dark-arm"); } if annotation.map(|a| a.semantic_churn > 0.0).unwrap_or(false) { @@ -6988,7 +6992,7 @@ fn coverage_background(annotation: &UiLineAnnotation, gutter: bool) -> String { } else { "rgba(34, 197, 94, 0.08)".to_string() } - } else if annotation.mutant_tested || annotation.mutant_killed_tests > 0 { + } else if annotation.covered && (annotation.mutant_tested || annotation.mutant_killed_tests > 0) { if gutter { "rgba(22, 101, 52, 0.34)".to_string() } else { @@ -7160,19 +7164,23 @@ fn inline_overlay_ranges( source: &str, annotation: &UiLineAnnotation, ) -> Vec { - let mut ranges = annotation - .dark_arm_spans - .iter() - .filter_map(|arm| { - let span = arm.span?; - dark_arm_line_range(line_no, source, span).map(|(start, end)| InlineOverlayRange { - start, - end, - classes: BTreeSet::from(["dark-arm-span".to_string()]), - labels: vec![arm.label.clone()], + let mut ranges = if annotation.covered { + annotation + .dark_arm_spans + .iter() + .filter_map(|arm| { + let span = arm.span?; + dark_arm_line_range(line_no, source, span).map(|(start, end)| InlineOverlayRange { + start, + end, + classes: BTreeSet::from(["dark-arm-span".to_string()]), + labels: vec![arm.label.clone()], + }) }) - }) - .collect::>(); + .collect::>() + } else { + Vec::new() + }; ranges.extend(annotation.effect_spans.iter().filter_map(|span| { let start = clamp_to_char_boundary(source, span.start.min(source.len())); let end = clamp_to_char_boundary(source, span.end.min(source.len())); @@ -9819,6 +9827,32 @@ flags: assert_eq!(gutter_bg, "transparent"); } + #[test] + fn coverage_background_unpainted_when_uncovered_even_with_mutant_tested() { + let mut annotation = empty_annotation(1); + annotation.covered = false; + annotation.mutant_tested = true; + + let bg = coverage_background(&annotation, false); + assert_eq!(bg, "transparent"); + + let gutter_bg = coverage_background(&annotation, true); + assert_eq!(gutter_bg, "transparent"); + } + + #[test] + fn highlight_source_line_with_dark_arms_skips_uncovered_lines() { + let mut annotation = empty_annotation(1); + annotation.covered = false; + annotation.dark_arm_spans = vec![UiDarkArm { + label: "dark arm: else".to_string(), + span: Some([0, 0, 0, 5]), + }]; + + let html = highlight_source_line_with_dark_arms("src/demo.rb", 1, "return x;", Some(&annotation)); + assert!(!html.contains("dark-arm-span")); + } + fn ui_file_for_sort(path: &str, tracked_lines: i64, covered_lines: i64, partial: i64) -> UiFile { UiFile { path: path.to_string(), From ace750eac5fcae31466ad6bdbf93a9a63dc84411 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Fri, 10 Jul 2026 00:26:58 +0000 Subject: [PATCH 08/65] Integrate scheduler.zig mutation testing, optimize workspace copying, and add post-commit update script Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/zig-mutants/src/main.zig | 13 ++-- gems/zig-mutants/subjects.json | 5 ++ package-lock.json | 6 +- tools/lineage_build_db.sh | 29 +++++++-- tools/lineage_update_commit.sh | 105 +++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 13 deletions(-) create mode 100755 tools/lineage_update_commit.sh diff --git a/gems/zig-mutants/src/main.zig b/gems/zig-mutants/src/main.zig index 8cf9c658a..bb0b8d614 100644 --- a/gems/zig-mutants/src/main.zig +++ b/gems/zig-mutants/src/main.zig @@ -735,13 +735,16 @@ fn copyWorkspace(allocator: Allocator, io: std.Io, root: []const u8, out_dir: [] \\mkdir -p "$1" && \\tar \ \\ --exclude=.git \ - \\ --exclude=.zig-cache \ - \\ --exclude=zig-cache \ - \\ --exclude=zig/.clear-cache \ - \\ --exclude=zig/zig-out \ + \\ --exclude="*/.zig-cache" \ + \\ --exclude="*/zig-cache" \ + \\ --exclude="*/.clear-cache" \ + \\ --exclude="*/zig-out" \ + \\ --exclude="*/target" \ + \\ --exclude="*/node_modules" \ + \\ --exclude="*/tmp" \ \\ -C "$0" -cf - . | tar -C "$1" -xf - ; - const argv = [_][]const u8{ "bash", "-lc", script, root, out_dir }; + const argv = [_][]const u8{ "bash", "-c", script, root, out_dir }; const result = try std.process.run(allocator, io, .{ .argv = &argv, .stdout_limit = .limited(1024 * 1024), diff --git a/gems/zig-mutants/subjects.json b/gems/zig-mutants/subjects.json index 6aa7fb332..5e8636452 100644 --- a/gems/zig-mutants/subjects.json +++ b/gems/zig-mutants/subjects.json @@ -39,6 +39,11 @@ "source": "zig/runtime/control-plane.zig", "test_command": "cd zig && zig build test-tsan -Dtest-file=control-plane-test.zig -j1 && zig build test-hammer -Dtest-file=control-plane-hammer-test.zig -j1", "timeout_seconds": 60 + }, + { + "source": "zig/runtime/scheduler.zig", + "test_command": "cd zig && zig build test-tsan -Dtest-file=scheduler-test.zig -j1 && zig build test-tsan -Dtest-file=scheduler-direct-test.zig -j1 && zig build test-tsan -Dtest-file=spsc-scheduler-test.zig -j1 && zig build test-hammer -Dtest-file=scheduler-primitives-hammer-test.zig -j1 && zig build test-loom-vopr -Dtest-file=scheduler-timeout-vopr-test.zig -j1", + "timeout_seconds": 300 } ] } diff --git a/package-lock.json b/package-lock.json index 717ce042c..c5be41849 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "litedb", + "name": "easy-vm", "lockfileVersion": 3, "requires": true, "packages": { @@ -61,10 +61,8 @@ } }, "node_modules/isexe": { - "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/node-addon-api": { "version": "8.9.0", diff --git a/tools/lineage_build_db.sh b/tools/lineage_build_db.sh index 0caa1a92f..9b5efaf85 100755 --- a/tools/lineage_build_db.sh +++ b/tools/lineage_build_db.sh @@ -10,8 +10,8 @@ COMMIT_HASH=$(git rev-parse HEAD) echo "=== [1/5] Running Tests & Generating Coverage ===" # Ruby spec coverage echo "Running Ruby specs under coverage..." -COVERAGE=1 bundle exec prspec spec/ -bundle exec ruby spec/collate_coverage.rb +COVERAGE=1 bundle exec prspec compiler/spec/ +bundle exec ruby compiler/spec/collate_coverage.rb # Zig unit/Loom/VOPR test coverage echo "Running Zig unit tests under coverage (kcov)..." @@ -19,7 +19,7 @@ cd zig zig build test -Dcoverage cd .. -echo "=== [2/5] Running Mutant Generators ===" +echo "=== [2/5] Running Mutant & SARIF Generators ===" # Go mutants echo "Running Go mutants..." ruby tools/mutants/go_mutants.rb @@ -32,6 +32,22 @@ bundle exec ruby tools/fuzz/mutants/run.rb --all --allow-dirty --exposure /tmp/c echo "Running transpile mutants..." bundle exec ruby tools/mutants/transpile_tests.rb --all --allow-dirty --exposure /tmp/clear-transpile-mutants-new.json --out /tmp/clear-transpile-mutants-out-new +# SARIF findings for SlopCop, Espalier, NilKill, Decomplex +echo "Building decomplex-rust release binary..." +cargo build --release --manifest-path gems/decomplex/Cargo.toml +ruby tools/generate_generalized_gem_sarif.rb \ + --repo . \ + --out-dir tmp/generalized-gems-sarif \ + --decomplex-binary gems/decomplex/target/release/decomplex-rust \ + --coverage /home/yahn/easy-vm/coverage/.resultset.json \ + --coverage /home/yahn/easy-vm/coverage/coverage.xml \ + --coverage /home/yahn/easy-vm/zig/zig-out/coverage/merged/kcov-merged/cobertura.xml \ + --coverage /tmp/cov-artifacts/ruby-gems/.resultset.json \ + --coverage /tmp/cov-artifacts/ruby-gems/coverage.xml \ + --coverage /tmp/cov-artifacts/fact/cobertura.xml \ + --coverage /tmp/cov-artifacts/decomplex/cobertura.xml \ + --coverage /tmp/cov-artifacts/lineage/cobertura.xml + echo "=== [3/5] Importing Codebase & Coverage into Lineage ===" gems/lineage/bin/lineage-import \ --repo . \ @@ -45,7 +61,8 @@ gems/lineage/bin/lineage-import \ --coverage /tmp/cov-artifacts/ruby-gems/coverage.xml \ --coverage /tmp/cov-artifacts/fact/cobertura.xml \ --coverage /tmp/cov-artifacts/decomplex/cobertura.xml \ - --coverage /tmp/cov-artifacts/lineage/cobertura.xml + --coverage /tmp/cov-artifacts/lineage/cobertura.xml \ + --sarif-input tmp/generalized-gems-sarif echo "=== [4/5] Ingesting Mutant & Test Exposure Facts ===" cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/clear-fuzz-mutants-new.json @@ -77,6 +94,10 @@ for i in {0..7}; do fi done +if [ -f /tmp/scheduler-mutants.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/scheduler-mutants.json --commit "$COMMIT_HASH" --test-type unit +fi + echo "=== [5/5] Refreshing UI Summaries ===" cargo run --release --manifest-path gems/lineage/Cargo.toml -- refresh-ui --db "$DB_PATH" diff --git a/tools/lineage_update_commit.sh b/tools/lineage_update_commit.sh new file mode 100755 index 000000000..6f021821e --- /dev/null +++ b/tools/lineage_update_commit.sh @@ -0,0 +1,105 @@ +#!/bin/bash +set -e + +# tools/lineage_update_commit.sh +# Updates the Lineage database for a new commit by running tests, generating coverage/SARIF, +# and ingesting the results, reusing pre-existing mutant facts. + +DB_PATH=${1:-/tmp/lineage.db} +COMMIT_HASH=$(git rev-parse HEAD) + +echo "=== [1/4] Running Tests & Generating Coverage ===" +# Ruby spec coverage +echo "Running Ruby specs under coverage..." +COVERAGE=1 bundle exec prspec compiler/spec/ +bundle exec ruby compiler/spec/collate_coverage.rb + +# Zig unit/Loom/VOPR test coverage +echo "Running Zig unit tests under coverage (kcov)..." +cd zig +zig build test -Dcoverage +cd .. + +echo "=== [2/4] Generating SARIF Findings ===" +# SARIF findings for SlopCop, Espalier, NilKill, Decomplex +echo "Building decomplex-rust release binary..." +cargo build --release --manifest-path gems/decomplex/Cargo.toml +ruby tools/generate_generalized_gem_sarif.rb \ + --repo . \ + --out-dir tmp/generalized-gems-sarif \ + --decomplex-binary gems/decomplex/target/release/decomplex-rust \ + --coverage /home/yahn/easy-vm/coverage/.resultset.json \ + --coverage /home/yahn/easy-vm/coverage/coverage.xml \ + --coverage /home/yahn/easy-vm/zig/zig-out/coverage/merged/kcov-merged/cobertura.xml \ + --coverage /tmp/cov-artifacts/ruby-gems/.resultset.json \ + --coverage /tmp/cov-artifacts/ruby-gems/coverage.xml \ + --coverage /tmp/cov-artifacts/fact/cobertura.xml \ + --coverage /tmp/cov-artifacts/decomplex/cobertura.xml \ + --coverage /tmp/cov-artifacts/lineage/cobertura.xml + +echo "=== [3/4] Importing Codebase & Coverage into Lineage ===" +gems/lineage/bin/lineage-import \ + --repo . \ + --db "$DB_PATH" \ + --out-dir tmp/lineage-import \ + --no-build-tools \ + --max-commits 100 \ + --coverage /home/yahn/easy-vm/coverage/coverage.xml \ + --coverage /home/yahn/easy-vm/zig/zig-out/coverage/merged/kcov-merged/cobertura.xml \ + --coverage /tmp/cov-artifacts/ruby-gems/.resultset.json \ + --coverage /tmp/cov-artifacts/ruby-gems/coverage.xml \ + --coverage /tmp/cov-artifacts/fact/cobertura.xml \ + --coverage /tmp/cov-artifacts/decomplex/cobertura.xml \ + --coverage /tmp/cov-artifacts/lineage/cobertura.xml \ + --sarif-input tmp/generalized-gems-sarif + +echo "=== [4/4] Ingesting Mutant & Test Exposure Facts ===" +if [ -f /tmp/clear-fuzz-mutants-new.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/clear-fuzz-mutants-new.json +fi +if [ -f /tmp/clear-transpile-mutants-new.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/clear-transpile-mutants-new.json +fi +if [ -f /tmp/zig-system-exposure.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-test-exposure --db "$DB_PATH" --repo . --commit "$COMMIT_HASH" --input /tmp/zig-system-exposure.json +fi + +# Stochastic/Unit mutant facts +if [ -f /tmp/rust-mutants-lineage-100/mutant-facts.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/rust-mutants-lineage-100/mutant-facts.json --commit "$COMMIT_HASH" --test-type unit +fi +if [ -f /tmp/boobytrap-go-mutants.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/boobytrap-go-mutants.json --commit "$COMMIT_HASH" --test-type unit +fi +if [ -f /tmp/litedb-mutant-facts-normalized.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/litedb-mutant-facts-normalized.json --commit "$COMMIT_HASH" --test-type unit +fi + +# PR 141 shards (if present) +for file in /tmp/pr141-*/mutant-facts.json; do + if [ -f "$file" ]; then + echo "Ingesting shard: $file" + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input "$file" --commit "$COMMIT_HASH" + fi +done + +# ClearParser mutants (if present) +if [ -f /tmp/ruby-mutants-parser-new/mutant-facts.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/ruby-mutants-parser-new/mutant-facts.json --commit "$COMMIT_HASH" +fi + +# Zig mutants shards +for i in {0..7}; do + if [ -f "/tmp/zig-mutants-runtime-final-normalized/shard-$i.json" ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input "/tmp/zig-mutants-runtime-final-normalized/shard-$i.json" --commit "$COMMIT_HASH" --test-type unit + fi +done + +if [ -f /tmp/scheduler-mutants.json ]; then + cargo run --release --manifest-path gems/lineage/Cargo.toml -- ingest-mutants --db "$DB_PATH" --repo . --input /tmp/scheduler-mutants.json --commit "$COMMIT_HASH" --test-type unit +fi + +echo "=== Refreshing UI Summaries ===" +cargo run --release --manifest-path gems/lineage/Cargo.toml -- refresh-ui --db "$DB_PATH" + +echo "=== Lineage Database Update Complete! ===" From 0eb10fc2454e9946c51ecc202e1f3e4b98764330 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Fri, 10 Jul 2026 10:03:56 +0000 Subject: [PATCH 09/65] Exclude dead findings from is_dark_arm classification and expand Ruby coverage generation Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/db/sarif.rs | 40 ++++++++++++++++++++++++++++++++++ tools/lineage_build_db.sh | 26 +++++++++++++++++++--- tools/lineage_update_commit.sh | 27 +++++++++++++++++++---- 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/gems/lineage/src/db/sarif.rs b/gems/lineage/src/db/sarif.rs index 83b4d49d9..eb5c3da22 100644 --- a/gems/lineage/src/db/sarif.rs +++ b/gems/lineage/src/db/sarif.rs @@ -388,6 +388,9 @@ fn is_dark_arm_result( .unwrap_or(category) ) .to_ascii_lowercase(); + if haystack.contains("dead") { + return false; + } haystack.contains("dark-arm") || haystack.contains("dark arm") } @@ -645,5 +648,42 @@ mod tests { assert_eq!(stats3.artifacts, 1); assert_eq!(stats3.findings, 2); + + let dead_json = dir.path().join("dead_test.sarif"); + fs::write( + &dead_json, + r#"{ + "version":"2.1.0", + "runs":[{ + "tool":{"driver":{"name":"SlopCop"}}, + "results":[{ + "ruleId":"slopcop.dark-arm.dead", + "level":"warning", + "message":{"text":"dark arm: dead"}, + "locations":[{ + "physicalLocation":{ + "artifactLocation":{"uri":"src/demo.rb"} + } + }], + "properties":{"kind":"dead"} + }] + }] + }"#, + ).unwrap(); + + let stats4 = ingest_sarif_paths( + &storage, + dir.path(), + &[dead_json], + "test_source", + "abc", + None, + false, + ).unwrap(); + + assert_eq!(stats4.findings, 1); + let findings = storage.sarif_findings_for_path("src/demo.rb").unwrap(); + let dead_finding = findings.iter().find(|f| f.rule_id == "slopcop.dark-arm.dead").unwrap(); + assert_eq!(dead_finding.is_dark_arm, false); } } diff --git a/tools/lineage_build_db.sh b/tools/lineage_build_db.sh index 9b5efaf85..e65607340 100755 --- a/tools/lineage_build_db.sh +++ b/tools/lineage_build_db.sh @@ -8,15 +8,35 @@ DB_PATH=${1:-/tmp/lineage.db} COMMIT_HASH=$(git rev-parse HEAD) echo "=== [1/5] Running Tests & Generating Coverage ===" -# Ruby spec coverage +# Clean old coverage results +rm -rf coverage/.resultset.json coverage/coverage.xml + +# Ruby spec coverage (unit + integration) echo "Running Ruby specs under coverage..." -COVERAGE=1 bundle exec prspec compiler/spec/ +COVERAGE=1 bundle exec prspec compiler/spec/ || true +COVERAGE=1 bundle exec prspec compiler/spec/ --tag integration || true + +# Transpile, corpus, and bc-lower coverages +echo "Running transpile-tests generation under coverage..." +COVERAGE=1 bundle exec ruby transpile-tests/gen.rb || true + +echo "Running corpus transpile coverage..." +COVERAGE=1 bundle exec ruby tools/corpus_transpile_coverage.rb || true + +echo "Running corpus runtime coverage..." +COVERAGE=1 bundle exec ruby tools/corpus_runtime_coverage.rb || true + +echo "Running bytecode lowering coverage..." +COVERAGE=1 bundle exec ruby tools/bc_lower_coverage.rb --jobs $(nproc) || true + +# Collate all Ruby coverage resultsets +echo "Collating Ruby coverage resultsets..." bundle exec ruby compiler/spec/collate_coverage.rb # Zig unit/Loom/VOPR test coverage echo "Running Zig unit tests under coverage (kcov)..." cd zig -zig build test -Dcoverage +zig build test -Dcoverage || true cd .. echo "=== [2/5] Running Mutant & SARIF Generators ===" diff --git a/tools/lineage_update_commit.sh b/tools/lineage_update_commit.sh index 6f021821e..75b4124ee 100755 --- a/tools/lineage_update_commit.sh +++ b/tools/lineage_update_commit.sh @@ -8,16 +8,35 @@ set -e DB_PATH=${1:-/tmp/lineage.db} COMMIT_HASH=$(git rev-parse HEAD) -echo "=== [1/4] Running Tests & Generating Coverage ===" -# Ruby spec coverage +# Clean old coverage results +rm -rf coverage/.resultset.json coverage/coverage.xml + +# Ruby spec coverage (unit + integration) echo "Running Ruby specs under coverage..." -COVERAGE=1 bundle exec prspec compiler/spec/ +COVERAGE=1 bundle exec prspec compiler/spec/ || true +COVERAGE=1 bundle exec prspec compiler/spec/ --tag integration || true + +# Transpile, corpus, and bc-lower coverages +echo "Running transpile-tests generation under coverage..." +COVERAGE=1 bundle exec ruby transpile-tests/gen.rb || true + +echo "Running corpus transpile coverage..." +COVERAGE=1 bundle exec ruby tools/corpus_transpile_coverage.rb || true + +echo "Running corpus runtime coverage..." +COVERAGE=1 bundle exec ruby tools/corpus_runtime_coverage.rb || true + +echo "Running bytecode lowering coverage..." +COVERAGE=1 bundle exec ruby tools/bc_lower_coverage.rb --jobs $(nproc) || true + +# Collate all Ruby coverage resultsets +echo "Collating Ruby coverage resultsets..." bundle exec ruby compiler/spec/collate_coverage.rb # Zig unit/Loom/VOPR test coverage echo "Running Zig unit tests under coverage (kcov)..." cd zig -zig build test -Dcoverage +zig build test -Dcoverage || true cd .. echo "=== [2/4] Generating SARIF Findings ===" From ea60838ec428f7d40b4948f5589c09f0ea06d5fc Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Fri, 10 Jul 2026 15:32:52 +0000 Subject: [PATCH 10/65] Implement native Cobertura branch partial coverage tracking and optimize import discovery Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/db/quality.rs | 34 ++++++++++++++++++++++--------- gems/lineage/src/db/storage.rs | 21 ++++++++++++++----- gems/lineage/src/ui/lsp.rs | 1 + gems/lineage/src/ui/ui.rs | 31 +++++++++++++++++----------- gems/lineage/tools/import_repo.rb | 1 + 5 files changed, 61 insertions(+), 27 deletions(-) diff --git a/gems/lineage/src/db/quality.rs b/gems/lineage/src/db/quality.rs index ac68b4ae8..0dedfb148 100644 --- a/gems/lineage/src/db/quality.rs +++ b/gems/lineage/src/db/quality.rs @@ -30,6 +30,7 @@ pub struct CoverageRecord { pub struct CoverageLineHit { pub line: u32, pub hits: u32, + pub is_partial: bool, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -176,6 +177,7 @@ fn ingest_records( &path, hit.line, hit.hits, + hit.is_partial, &options.line_source, )?); } @@ -300,6 +302,7 @@ fn parse_simplecov_records(value: &Value) -> Vec { hits.map(|hits| CoverageLineHit { line: (index + 1) as u32, hits, + is_partial: false, }) }) .collect::>(); @@ -384,7 +387,7 @@ fn line_hits_from_generic_node(node: &Value) -> Vec { if let Some(number) = line.as_u64() { return u32::try_from(number) .ok() - .map(|line| CoverageLineHit { line, hits: 1 }); + .map(|line| CoverageLineHit { line, hits: 1, is_partial: false }); } let line_no = line @@ -402,6 +405,7 @@ fn line_hits_from_generic_node(node: &Value) -> Vec { Some(CoverageLineHit { line: line_no, hits, + is_partial: false, }) }) .collect() @@ -457,9 +461,19 @@ fn parse_cobertura_records(input: &str) -> Result> { .filter_map(|line| { let line_no = line.attribute("number")?.parse::().ok()?; let hits = line.attribute("hits").unwrap_or("0").parse::().ok()?; + let is_partial = if line.attribute("branch").unwrap_or("false") == "true" { + if let Some(cc) = line.attribute("condition-coverage") { + !cc.starts_with("100%") + } else { + false + } + } else { + false + }; Some(CoverageLineHit { line: line_no, hits, + is_partial, }) }) .collect::>(); @@ -660,8 +674,8 @@ mod tests { let records = parse_coverage_records(&value, "generic").unwrap(); assert_eq!(records.len(), 1); - assert_eq!(records[0].line_hits[0], CoverageLineHit { line: 1, hits: 2 }); - assert_eq!(records[0].line_hits[1], CoverageLineHit { line: 2, hits: 0 }); + assert_eq!(records[0].line_hits[0], CoverageLineHit { line: 1, hits: 2, is_partial: false }); + assert_eq!(records[0].line_hits[1], CoverageLineHit { line: 2, hits: 0, is_partial: false }); } #[test] @@ -685,8 +699,8 @@ mod tests { assert_eq!(records.len(), 1); assert_eq!(records[0].path, "src/demo.rb"); assert_eq!(records[0].line_coverage, Some(50.0)); - assert_eq!(records[0].line_hits[0], CoverageLineHit { line: 1, hits: 3 }); - assert_eq!(records[0].line_hits[1], CoverageLineHit { line: 2, hits: 0 }); + assert_eq!(records[0].line_hits[0], CoverageLineHit { line: 1, hits: 3, is_partial: false }); + assert_eq!(records[0].line_hits[1], CoverageLineHit { line: 2, hits: 0, is_partial: false }); } #[test] @@ -740,9 +754,9 @@ mod tests { assert_eq!( records[0].line_hits, vec![ - CoverageLineHit { line: 2, hits: 2 }, - CoverageLineHit { line: 3, hits: 3 }, - CoverageLineHit { line: 5, hits: 1 }, + CoverageLineHit { line: 2, hits: 2, is_partial: false }, + CoverageLineHit { line: 3, hits: 3, is_partial: false }, + CoverageLineHit { line: 5, hits: 1, is_partial: false }, ] ); assert_eq!(records[0].line_coverage, Some(100.0)); @@ -757,8 +771,8 @@ mod tests { mutant_coverage: None, hard_gated: None, line_hits: vec![ - CoverageLineHit { line: 1, hits: 2 }, - CoverageLineHit { line: 2, hits: 0 }, + CoverageLineHit { line: 1, hits: 2, is_partial: false }, + CoverageLineHit { line: 2, hits: 0, is_partial: false }, ], }]; diff --git a/gems/lineage/src/db/storage.rs b/gems/lineage/src/db/storage.rs index 0b5c31b88..98a9c0dcc 100644 --- a/gems/lineage/src/db/storage.rs +++ b/gems/lineage/src/db/storage.rs @@ -188,6 +188,7 @@ impl Storage { path TEXT NOT NULL, line INTEGER NOT NULL, hits INTEGER NOT NULL, + is_partial INTEGER NOT NULL DEFAULT 0, source TEXT NOT NULL DEFAULT 'coverage', UNIQUE(commit_hash, path, line, source) ); @@ -1365,7 +1366,7 @@ impl Storage { line: u32, hits: u32, ) -> Result { - self.record_coverage_line_with_source(commit_hash, timestamp, path, line, hits, "coverage") + self.record_coverage_line_with_source(commit_hash, timestamp, path, line, hits, false, "coverage") } pub fn record_coverage_line_with_source( @@ -1375,20 +1376,30 @@ impl Storage { path: &str, line: u32, hits: u32, + is_partial: bool, source: &str, ) -> Result { let changed = self.conn.execute( r#" INSERT INTO coverage_line_events - (commit_hash, timestamp, path, line, hits, source) - VALUES (?1, ?2, ?3, ?4, ?5, ?6) + (commit_hash, timestamp, path, line, hits, is_partial, source) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) ON CONFLICT(commit_hash, path, line, source) DO UPDATE SET timestamp = MAX(coverage_line_events.timestamp, excluded.timestamp), - hits = MAX(coverage_line_events.hits, excluded.hits) + hits = MAX(coverage_line_events.hits, excluded.hits), + is_partial = MAX(coverage_line_events.is_partial, excluded.is_partial) WHERE excluded.timestamp > coverage_line_events.timestamp OR excluded.hits > coverage_line_events.hits "#, - params![commit_hash, timestamp, path, line, hits, source], + params![ + commit_hash, + timestamp, + path, + line, + hits, + if is_partial { 1 } else { 0 }, + source + ], )?; Ok(changed > 0) } diff --git a/gems/lineage/src/ui/lsp.rs b/gems/lineage/src/ui/lsp.rs index 4dc7e3f12..a3c29204b 100644 --- a/gems/lineage/src/ui/lsp.rs +++ b/gems/lineage/src/ui/lsp.rs @@ -550,6 +550,7 @@ mod tests { UiLineAnnotation { line: 7, covered: true, + is_partial: false, mutant_tested: true, test_types: vec!["unit".to_string()], distinct_tests: 2, diff --git a/gems/lineage/src/ui/ui.rs b/gems/lineage/src/ui/ui.rs index e7bdb144b..7aee0ed82 100644 --- a/gems/lineage/src/ui/ui.rs +++ b/gems/lineage/src/ui/ui.rs @@ -341,6 +341,7 @@ pub struct UiEffectSpan { pub struct UiLineAnnotation { pub line: u32, pub covered: bool, + pub is_partial: bool, pub mutant_tested: bool, pub test_types: Vec, pub distinct_tests: i64, @@ -688,6 +689,7 @@ struct SourceQuery { #[derive(Default)] struct AnnotationBuilder { covered: bool, + is_partial: bool, mutant_tested: bool, test_types: BTreeSet, distinct_tests: i64, @@ -3238,6 +3240,7 @@ pub fn line_annotations( .map(|(line, builder)| UiLineAnnotation { line, covered: builder.covered, + is_partial: builder.is_partial, mutant_tested: builder.mutant_tested, test_types: builder.test_types.into_iter().collect(), distinct_tests: builder.distinct_tests, @@ -3324,6 +3327,7 @@ fn empty_annotation(line: u32) -> UiLineAnnotation { UiLineAnnotation { line, covered: false, + is_partial: false, mutant_tested: false, test_types: Vec::new(), distinct_tests: 0, @@ -3476,7 +3480,7 @@ fn apply_line_coverage( let mut stmt = storage.connection().prepare( r#" WITH latest_source AS ( - SELECT line, source, hits, + SELECT line, source, hits, is_partial, ROW_NUMBER() OVER ( PARTITION BY line, source ORDER BY timestamp DESC, id DESC @@ -3485,29 +3489,32 @@ fn apply_line_coverage( WHERE path = ?1 ), latest AS ( - SELECT line, MAX(hits) AS hits + SELECT line, MAX(hits) AS hits, MAX(is_partial) AS is_partial FROM latest_source WHERE rank = 1 GROUP BY line ) - SELECT line, hits + SELECT line, hits, COALESCE(is_partial, 0) FROM latest ORDER BY line "#, )?; let rows = stmt.query_map(params![path], |row| { - Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)) + Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?, row.get::<_, i64>(2)?)) })?; let mut has_exact_line_coverage = false; for row in rows { - let (line, hits) = row?; + let (line, hits, is_partial) = row?; has_exact_line_coverage = true; let entry = lines.entry(line).or_default(); entry.line_hits = Some(hits); if hits > 0 { entry.covered = true; } + if is_partial != 0 { + entry.is_partial = true; + } } Ok(has_exact_line_coverage) } @@ -5464,7 +5471,7 @@ fn source_coverage_context(payload: &UiSourcePayload) -> UiCoverageContext { .iter() .filter(|annotation| { (!has_exact_line_hits || annotation.line_hits.is_some()) - && annotation_has_dark_arms(annotation) + && annotation.is_partial }) .count() as i64; let partial_lines = partial_lines.clamp(0, covered_lines); @@ -6174,7 +6181,7 @@ fn render_code_line( if annotation.map(|a| a.mutant_tested).unwrap_or(false) { classes.push("mutant"); } - if annotation.map(|a| a.covered && annotation_has_dark_arms(a)).unwrap_or(false) { + if annotation.map(|a| a.covered && a.is_partial).unwrap_or(false) { classes.push("dark-arm"); } if annotation.map(|a| a.semantic_churn > 0.0).unwrap_or(false) { @@ -6986,7 +6993,7 @@ fn row_style(annotation: &UiLineAnnotation) -> String { } fn coverage_background(annotation: &UiLineAnnotation, gutter: bool) -> String { - if annotation.covered && annotation_has_dark_arms(annotation) { + if annotation.covered && annotation.is_partial { if gutter { "rgba(31, 41, 55, 0.32)".to_string() } else { @@ -7073,10 +7080,6 @@ fn line_has_details(annotation: &UiLineAnnotation) -> bool { || !annotation.bug_events.is_empty() } -fn annotation_has_dark_arms(annotation: &UiLineAnnotation) -> bool { - !annotation.dark_arms.is_empty() || !annotation.dark_arm_spans.is_empty() -} - fn dark_arm_labels(annotation: &UiLineAnnotation) -> Vec { let mut labels = annotation.dark_arms.clone(); labels.extend(annotation.dark_arm_spans.iter().map(|arm| arm.label.clone())); @@ -8612,6 +8615,7 @@ mod tests { annotations: vec![UiLineAnnotation { line: 2, covered: true, + is_partial: false, mutant_tested: false, test_types: vec!["fuzz".to_string(), "integration".to_string(), "unit".to_string()], distinct_tests: 9, @@ -9655,6 +9659,7 @@ flags: let mut annotations = vec![UiLineAnnotation { line: 1, covered: true, + is_partial: false, mutant_tested: false, test_types: Vec::new(), distinct_tests: 0, @@ -9723,6 +9728,7 @@ flags: let annotation = UiLineAnnotation { line: 1, covered: true, + is_partial: true, mutant_tested: false, test_types: Vec::new(), distinct_tests: 0, @@ -9805,6 +9811,7 @@ flags: fn coverage_background_paints_partial_coverage_when_dark_arms_exist() { let mut annotation = empty_annotation(1); annotation.covered = true; + annotation.is_partial = true; annotation.dark_arms = vec!["dark arm: else".to_string()]; let bg = coverage_background(&annotation, false); diff --git a/gems/lineage/tools/import_repo.rb b/gems/lineage/tools/import_repo.rb index 0cb5fad47..66baa2494 100755 --- a/gems/lineage/tools/import_repo.rb +++ b/gems/lineage/tools/import_repo.rb @@ -21,6 +21,7 @@ node_modules target vendor + bc-lower-shards ].freeze SOURCE_EXTS = { From aae7113e51cf4887a6a5768febf264ee193e0119 Mon Sep 17 00:00:00 2001 From: Brian Yahn Date: Fri, 10 Jul 2026 16:16:52 +0000 Subject: [PATCH 11/65] Add collapsible sidebar, recycle icon for reentrant functions, function folding, and private function layer controls Co-authored-by: gemini-cli <218195315+gemini-cli@users.noreply.github.com> --- gems/lineage/src/ui/assets/app.css | 112 ++++++++++ gems/lineage/src/ui/assets/app.js | 52 ++++- gems/lineage/src/ui/templates/app.html | 9 + .../src/ui/templates/dashboard_sidebar.html | 5 - .../lineage/src/ui/templates/layers_menu.html | 5 + .../src/ui/templates/source_sidebar.html | 5 - .../lineage/src/ui/templates/source_view.html | 1 + gems/lineage/src/ui/ui.rs | 195 ++++++++++++++++-- 8 files changed, 361 insertions(+), 23 deletions(-) diff --git a/gems/lineage/src/ui/assets/app.css b/gems/lineage/src/ui/assets/app.css index 22de45ca4..7feb8cbca 100644 --- a/gems/lineage/src/ui/assets/app.css +++ b/gems/lineage/src/ui/assets/app.css @@ -30,6 +30,65 @@ height: 100vh; min-height: 0; overflow: hidden; + position: relative; + } + .app:has(.sidebar-toggle:not(:checked)) { + grid-template-columns: 0px 1fr; + } + .sidebar-toggle { + display: none; + } + .sidebar-collapse-container { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 8px 12px; + border-bottom: 1px solid var(--line); + background: var(--panel); + } + .sidebar-collapse-btn { + font-size: 11px; + color: var(--muted); + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; + user-select: none; + } + .sidebar-collapse-btn:hover { + color: var(--text); + } + .sidebar-expand-btn { + display: none; + position: absolute; + left: 8px; + top: 8px; + z-index: 1000; + width: 28px; + height: 28px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 6px; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 1px 4px rgba(0,0,0,0.1); + color: var(--muted); + } + .sidebar-expand-btn:hover { + color: var(--text); + background: var(--hover); + } + .sidebar-toggle:not(:checked) ~ aside { + display: none !important; + } + .sidebar-toggle:not(:checked) ~ main { + grid-column: 1 / span 2; + padding-left: 44px; + position: relative; + } + .sidebar-toggle:not(:checked) ~ main .sidebar-expand-btn { + display: flex; } aside { border-right: 1px solid var(--line); @@ -869,6 +928,59 @@ #layer-comment-folding:checked ~ .viewer .row.comment-fold-hidden { display: none; } + .fn-fold-toggle { display: none; } + .fn-fold-control { + border-radius: 3px; + color: #475569; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 700; + inline-size: 18px; + min-height: 18px; + } + .fn-fold-control *, + .fn-fold-arrow, + .fn-fold-arrow::before, + .fn-fold-control::before, + .fn-fold-control::after { + cursor: pointer; + } + .fn-fold-control:hover, + .fn-fold-control:focus-visible { + background: rgba(71, 85, 105, 0.14); + color: #0f172a; + outline: 1px solid rgba(71, 85, 105, 0.28); + } + .fn-fold-arrow::before { + content: ">"; + display: inline-block; + inline-size: 12px; + text-align: center; + transform: rotate(0deg); + transition: transform 120ms ease; + } + .fn-fold-toggle:checked ~ .ln .fn-fold-arrow::before { transform: rotate(0deg); } + .fn-fold-toggle:not(:checked) ~ .ln .fn-fold-arrow::before { transform: rotate(90deg); } + .fn-fold-toggle:checked ~ .ln, + .fn-fold-toggle:checked ~ .gutter, + .fn-fold-toggle:checked ~ .source-text, + .fn-fold-toggle:checked ~ .blame-cell { border-bottom: 1px dashed rgba(100, 116, 139, 0.28); } + .row.fn-fold-hidden { + display: none !important; + } + #layer-comment-folding:checked ~ .viewer .row.comment-fold-hidden.fn-fold-child { + display: none; + } + .outline a.private-symbol .outline-name { + color: var(--muted); + } + #layer-private-folding:checked ~ .topbar .private-fold-layer .switch-on { display: inline-block; } + #layer-private-folding:checked ~ .topbar .private-fold-layer .switch-off { display: none; } + #layer-private-folding:not(:checked) ~ .topbar .private-fold-layer .switch-on { display: none; } + #layer-private-folding:not(:checked) ~ .topbar .private-fold-layer .switch-off { display: inline-block; } + .row.dark-arm .line-meta { color: var(--dark-arm); font-weight: 700; diff --git a/gems/lineage/src/ui/assets/app.js b/gems/lineage/src/ui/assets/app.js index 2068762f1..6d2580361 100644 --- a/gems/lineage/src/ui/assets/app.js +++ b/gems/lineage/src/ui/assets/app.js @@ -110,6 +110,40 @@ input.addEventListener("change", () => setFoldRows(input)); }; + const setFnFoldRows = (input) => { + const foldId = input.dataset.foldId; + const sourceView = input.closest(".source-view"); + const row = input.closest(".row"); + if (row) { + row.classList.toggle("fn-fold-collapsed", input.checked); + row.classList.toggle("fn-fold-expanded", !input.checked); + } + if (!foldId || !sourceView) return; + sourceView + .querySelectorAll(`[data-fn-fold-child="${foldId}"]`) + .forEach((row) => row.classList.toggle("fn-fold-hidden", input.checked)); + }; + + const restoreFnFold = (input) => { + const key = input.dataset.persistKey; + const stored = key ? read(key) : null; + if (stored === null) { + if (input.classList.contains("private-fn-fold")) { + const privateFoldingLayer = document.getElementById("layer-private-folding"); + input.checked = privateFoldingLayer ? privateFoldingLayer.checked : true; + } else { + input.checked = false; + } + } else { + input.checked = (stored === "true"); + } + input.addEventListener("change", () => { + if (key) write(key, String(input.checked)); + setFnFoldRows(input); + }); + setFnFoldRows(input); + }; + const restoreWarningDismissal = (control) => { const key = control.dataset.dismissKey; const warning = control.closest(".warning"); @@ -187,9 +221,25 @@ document.addEventListener("DOMContentLoaded", () => { document - .querySelectorAll("input[data-persist-key]:not(.comment-fold-toggle)") + .querySelectorAll("input[data-persist-key]:not(.comment-fold-toggle):not(.fn-fold-toggle)") .forEach(restoreInput); document.querySelectorAll(".comment-fold-toggle[data-persist-key]").forEach(restoreCommentFold); + document.querySelectorAll(".fn-fold-toggle[data-persist-key]").forEach(restoreFnFold); + + const privateFoldingLayer = document.getElementById("layer-private-folding"); + if (privateFoldingLayer) { + privateFoldingLayer.addEventListener("change", () => { + document.querySelectorAll(".private-fn-fold").forEach(input => { + const key = input.dataset.persistKey; + const stored = key ? read(key) : null; + if (stored === null) { + input.checked = privateFoldingLayer.checked; + input.dispatchEvent(new Event("change", { bubbles: true })); + } + }); + }); + } + document.querySelectorAll(".warning-dismiss[data-dismiss-key]").forEach(restoreWarningDismissal); document.querySelectorAll(".layers-panel label[for]").forEach(bindLayerLabel); document.querySelectorAll(".line-toggle").forEach(bindLineToggle); diff --git a/gems/lineage/src/ui/templates/app.html b/gems/lineage/src/ui/templates/app.html index a9b4d3c1f..1469eb052 100644 --- a/gems/lineage/src/ui/templates/app.html +++ b/gems/lineage/src/ui/templates/app.html @@ -1,8 +1,17 @@
+ + {{ sidebar|safe }}
+ {{ main|safe }}
diff --git a/gems/lineage/src/ui/templates/dashboard_sidebar.html b/gems/lineage/src/ui/templates/dashboard_sidebar.html index a7602addc..cd64c659b 100644 --- a/gems/lineage/src/ui/templates/dashboard_sidebar.html +++ b/gems/lineage/src/ui/templates/dashboard_sidebar.html @@ -1,8 +1,3 @@ -
-

Lineage

-
{{ summary }}
- {{ nav|safe }} -
{% if show_directory_input %} diff --git a/gems/lineage/src/ui/templates/layers_menu.html b/gems/lineage/src/ui/templates/layers_menu.html index 7c568919f..a2c1a9f32 100644 --- a/gems/lineage/src/ui/templates/layers_menu.html +++ b/gems/lineage/src/ui/templates/layers_menu.html @@ -27,5 +27,10 @@ On Off + diff --git a/gems/lineage/src/ui/templates/source_sidebar.html b/gems/lineage/src/ui/templates/source_sidebar.html index dca03e135..c9e0c35ba 100644 --- a/gems/lineage/src/ui/templates/source_sidebar.html +++ b/gems/lineage/src/ui/templates/source_sidebar.html @@ -1,8 +1,3 @@ -
-

Lineage

-
{{ path }}
- {{ nav|safe }} -
{% if show_empty_outline %}