// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Runs the Check Coverage workspace validation tool. library; import 'dart:io'; /// Checks an LCOV report against a package-library coverage threshold. void main(List arguments) { if (arguments.length != 2) { stderr.writeln('Usage: dart run tool/check_coverage.dart '); exitCode = 64; return; } final lcovFile = File(arguments[0]); final threshold = double.tryParse(arguments[1]); if (threshold == null) { stderr.writeln('Coverage threshold must be numeric: ${arguments[1]}'); exitCode = 64; return; } if (!lcovFile.existsSync()) { stderr.writeln('Coverage file not found: ${lcovFile.path}'); exitCode = 1; return; } final coverage = _readCoverage(lcovFile); if (coverage.totalLines == 0) { stderr.writeln('Coverage file has no package lib/ lines.'); exitCode = 1; return; } final percent = coverage.percent; stdout.writeln( 'Package lib coverage: ${percent.toStringAsFixed(2)}% ' '(${coverage.coveredLines}/${coverage.totalLines})', ); if (percent < threshold) { stderr.writeln( 'Coverage ${percent.toStringAsFixed(2)}% is below ' '${threshold.toStringAsFixed(2)}%.', ); exitCode = 1; } } /// Runs the `this operation` top-level operation. /// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior. ({int coveredLines, int totalLines, double percent}) _readCoverage(File file) { var includeFile = false; var coveredLines = 0; var totalLines = 0; for (final line in file.readAsLinesSync()) { if (line.startsWith('SF:')) { includeFile = _includeSourceFile(line.substring(3)); continue; } if (!includeFile || !line.startsWith('DA:')) { continue; } final comma = line.indexOf(','); if (comma == -1) { continue; } totalLines += 1; final hits = int.tryParse(line.substring(comma + 1).split(',').first) ?? 0; if (hits > 0) { coveredLines += 1; } } final percent = totalLines == 0 ? 0.0 : coveredLines * 100 / totalLines; return ( coveredLines: coveredLines, totalLines: totalLines, percent: percent, ); } /// Top-level helper that performs the `_includeSourceFile` operation for this file. /// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. bool _includeSourceFile(String path) { final normalized = path.replaceAll('\\', '/'); return normalized.contains('/packages/') && normalized.contains('/lib/') && !normalized.endsWith('.g.dart') && !_isBuilderInputSource(normalized); } /// Reports whether [path] is source input for code generation, not runtime code. /// /// Drift table declarations are intentionally consumed by the builder and then /// executed through generated database classes. Runtime schema behavior is /// covered by SQLite open and migration tests, while the generated files remain /// excluded by the existing `.g.dart` rule above. bool _isBuilderInputSource(String path) { return path.contains( '/packages/scheduler_persistence_sqlite/lib/src/scheduler_db/tables/', ); }