lib/pronto/rustcov.rb
require 'pronto' module Pronto class Rustcov < Runner def run one_message_per_file(lcov_path) end private def lcov_path ENV['PRONTO_RUSTCOV_LCOV_PATH'] || ENV['LCOV_PATH'] || 'target/lcov.info' end def pronto_files_limit ENV['PRONTO_RUSTCOV_FILES_LIMIT']&.to_i || 5 end def pronto_messages_per_file_limit ENV['PRONTO_RUSTCOV_MESSAGES_PER_FILE_LIMIT']&.to_i || 5 end def one_message_per_file(lcov_path) return [] unless @patches lcov = parse_lcov(lcov_path) grouped = group_patches(@patches, lcov) build_messages(grouped) end def group_patches(patches, lcov) grouped = Hash.new { |h, k| h[k] = [] } patches.each do |patch| next unless patch.added_lines.any? file_path = patch.new_file_full_path.to_s uncovered = lcov[file_path] next unless uncovered patch.added_lines.each do |line| if uncovered.include?(line.new_lineno) grouped[patch].push(line) end end end grouped.sort_by { |_, lines| -lines.count }.take(pronto_files_limit) end def build_messages(grouped) messages = [] grouped.each do |patch, lines| linenos = lines.map(&:new_lineno).sort line_ranges = linenos.chunk_while { |i, j| j == i + 1 }.to_a best_ranges = line_ranges.sort_by { |range| [-range.size, range.first] }.take(pronto_messages_per_file_limit) # If we have a message per file limit of N, then create N individual messages # We'll take each range and create a separate message for it, up to the limit best_ranges.each do |range| message_text = format_message_text(range) # Find the first line in this range for the message first_line_in_range = lines.find { |line| line.new_lineno == range.first } messages << Pronto::Message.new( patch.new_file_path, first_line_in_range, :warning, message_text, nil, self.class ) end end messages end def format_message_text(range) if range.size > 1 "⚠️ Test coverage is missing for lines: #{range.first}–#{range.last}" else "⚠️ Test coverage is missing" end end def parse_lcov(path) uncovered = Hash.new { |h, k| h[k] = [] } file = nil begin File.foreach(path) do |line| case line when /^SF:(.+)/ file = File.expand_path($1.strip) when /^DA:(\d+),0$/ uncovered[file] << $1.to_i if file when /^end_of_record/ file = nil end end rescue Errno::ENOENT # File not found, raise a more informative error fail "LCOV file not found at #{path}. Make sure your Rust tests were run with coverage enabled." end uncovered end end end