Class: CovLoupe::StalenessChecker
- Inherits:
-
Object
- Object
- CovLoupe::StalenessChecker
- Defined in:
- lib/cov_loupe/staleness/staleness_checker.rb
Overview
Lightweight service object to check staleness of coverage vs. sources.
Staleness detection operates at two levels:
- File level: checks if a single source file is newer than the coverage timestamp
or has a different line count than the coverage array
- Project level: scans all tracked files for newer/missing/deleted/mismatched files
Modes:
- :off → compute staleness but never raise (used for status reporting)
- :error → raise CoverageDataStaleError or CoverageDataProjectStaleError on issues
Staleness categories (mutually exclusive per file):
- 'ok' → file is fresh and line counts match
- 'missing' → source file deleted since coverage was recorded
- 'newer' → source mtime exceeds coverage timestamp (only when no length mismatch)
- 'length_mismatch'→ source line count differs from coverage array length
- 'error' → file could not be read (permissions, I/O)
Constant Summary collapse
- MODES =
%i[off error].freeze
Instance Method Summary collapse
-
#check_file!(file_abs, coverage_lines) ⇒ Object
Raise CoverageDataStaleError if stale (only in error mode).
-
#check_project!(coverage_map) ⇒ Object
Compute and return project staleness details (newer, missing, deleted files).
-
#check_project_with_lines!(coverage_lines_by_path, coverage_files:) ⇒ Object
Compute and return project staleness details including line-count mismatches.
-
#file_staleness_status(file_abs, coverage_lines) ⇒ Object
Compute the staleness status for a specific file relative to coverage.
-
#initialize(root:, resultset:, mode: :off, tracked_globs: nil, timestamp: nil) ⇒ StalenessChecker
constructor
A new instance of StalenessChecker.
- #off? ⇒ Boolean
Constructor Details
#initialize(root:, resultset:, mode: :off, tracked_globs: nil, timestamp: nil) ⇒ StalenessChecker
Returns a new instance of StalenessChecker.
30 31 32 33 34 35 36 37 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 30 def initialize(root:, resultset:, mode: :off, tracked_globs: nil, timestamp: nil) @root = File.(root || '.') @resultset = resultset @mode = (mode || :off).to_sym @tracked_globs = tracked_globs @cov_timestamp = @resultset_path = nil end |
Instance Method Details
#check_file!(file_abs, coverage_lines) ⇒ Object
Raise CoverageDataStaleError if stale (only in error mode)
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 44 def check_file!(file_abs, coverage_lines) return if off? d = compute_file_staleness_details(file_abs, coverage_lines) # Raise FileError if there was a read error if d[:read_error] raise FileError, "Error reading file: #{rel(file_abs)}" end # For single-file checks, missing files with recorded coverage count as stale # via length mismatch; project-level checks also handle deleted files explicitly. if d[:newer] || d[:len_mismatch] raise CoverageDataStaleError.new( nil, nil, file_path: rel(file_abs), file_mtime: d[:file_mtime], cov_timestamp: d[:coverage_timestamp], src_len: d[:src_len], cov_len: d[:cov_len], resultset_path: resultset_path ) end end |
#check_project!(coverage_map) ⇒ Object
Compute and return project staleness details (newer, missing, deleted files). If in error mode, raises CoverageDataProjectStaleError when issues are found. Returns a hash { newer_files: [], missing_files: [], deleted_files: [], unreadable_files: [] }
Does not check line counts. Use check_project_with_lines! for full staleness detection.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 92 def check_project!(coverage_map) ts = coverage_files = coverage_map.keys newer, deleted, unreadable = compute_newer_and_deleted_files(coverage_files, ts) missing = compute_missing_files(coverage_files) staleness_details = { newer_files: newer, missing_files: missing, deleted_files: deleted, unreadable_files: unreadable, timestamp_status: ts.to_i > 0 ? 'ok' : 'missing', } if @mode == :error && (newer.any? || missing.any? || deleted.any? || unreadable.any?) raise CoverageDataProjectStaleError.new( nil, nil, cov_timestamp: ts, newer_files: newer, missing_files: missing, deleted_files: deleted, unreadable_files: unreadable, resultset_path: resultset_path ) end staleness_details end |
#check_project_with_lines!(coverage_lines_by_path, coverage_files:) ⇒ Object
Compute and return project staleness details including line-count mismatches. If in error mode, raises CoverageDataProjectStaleError when issues are found. Returns a hash with newer/missing/deleted/mismatched/unreadable files and per-file statuses.
Deduplication: files that are both "newer" AND have a length mismatch are reported only as "length_mismatch" (the more specific diagnosis). This prevents a file from appearing in both the newer_files and length_mismatch_files lists.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 130 def check_project_with_lines!(coverage_lines_by_path, coverage_files:) coverage_lines_by_path ||= {} ts = newer, deleted, unreadable = compute_newer_and_deleted_files(coverage_files, ts) missing = compute_missing_files(coverage_files) file_statuses = {} length_mismatch = [] coverage_lines_by_path.each do |abs_path, coverage_lines| details = compute_file_staleness_details(abs_path, coverage_lines) status = if details[:read_error] 'error' elsif !details[:exists] 'missing' elsif details[:newer] 'newer' elsif details[:len_mismatch] 'length_mismatch' else 'ok' end file_statuses[abs_path] = status unreadable << rel(abs_path) if details[:read_error] length_mismatch << rel(abs_path) if details[:len_mismatch] && details[:exists] end # A file can be both newer than the coverage timestamp and otherwise broken (for # example unreadable or with a different line count). Keep the more actionable # per-file diagnosis and suppress the broader "newer" label to avoid duplicates. newer -= length_mismatch newer -= unreadable staleness_details = { newer_files: newer, missing_files: missing, deleted_files: deleted, length_mismatch_files: length_mismatch, unreadable_files: unreadable, file_statuses: file_statuses, timestamp_status: ts.to_i > 0 ? 'ok' : 'missing', } if @mode == :error && [newer, missing, deleted, length_mismatch, unreadable].any?(&:any?) raise CoverageDataProjectStaleError.new( nil, nil, cov_timestamp: ts, newer_files: newer, missing_files: missing, deleted_files: deleted, length_mismatch_files: length_mismatch, unreadable_files: unreadable, resultset_path: resultset_path ) end staleness_details end |
#file_staleness_status(file_abs, coverage_lines) ⇒ Object
Compute the staleness status for a specific file relative to coverage. Ignores mode and never raises. Returns a String:
- 'ok' - file is not stale (fresh)
- 'missing' - the file is missing/deleted
- 'newer' - the file mtime is newer than the coverage timestamp
- 'length_mismatch' - the source line count differs from coverage lines array length
- 'error' - the file cannot be read due to permission or I/O errors
77 78 79 80 81 82 83 84 85 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 77 def file_staleness_status(file_abs, coverage_lines) d = compute_file_staleness_details(file_abs, coverage_lines) return 'error' if d[:read_error] return 'missing' unless d[:exists] return 'newer' if d[:newer] return 'length_mismatch' if d[:len_mismatch] 'ok' end |
#off? ⇒ Boolean
39 40 41 |
# File 'lib/cov_loupe/staleness/staleness_checker.rb', line 39 def off? @mode == :off end |