With several --dir values, one that did not exist was skipped silently: the no-files guard only fires when every directory is empty, so `--dir lib --dir tset` scanned lib and reported as though it had covered what was asked for. A --dir that does not exist is now an error naming it. A directory that could not be listed, or a baseline that could not be read or written, escaped as an uncaught FileSystemException: exit 255 and a stack trace. Those paths now raise a PipeException with the OS reason (exit 2), and bin/pipe.dart catches any remaining FileSystemException the same way. The exit codes are documented in bin/pipe.dart: 0 success, 1 a gate failed, 2 pipe could not do what was asked, 64 usage. rule_scope_claim_test widened the fixture with a test/ directory it does not have, relying on exactly the silent skip; it now builds a project that has one.
86 lines
3.1 KiB
Dart
86 lines
3.1 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:pipe/pipe.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
/// A finding's claim must not be wider than the evidence behind it.
|
|
///
|
|
/// The dead-handler message used to say "nothing anywhere ever dispatches or
|
|
/// constructs X" while the analysis only ever scanned `--dir` (default `lib`).
|
|
/// On a real app that overclaim was false for 67 of 77 findings, because their
|
|
/// events are constructed by tests - and it nearly justified 77 needless UI
|
|
/// wirings, and the deletion of code covered by seeded fuzz tests.
|
|
void main() {
|
|
test(
|
|
'the dead-handler message names the directories actually scanned',
|
|
() async {
|
|
final project = await loadProject('test/fixtures/stuck_loading');
|
|
final analysis = await analyzeProject(project, concurrency: 1);
|
|
try {
|
|
final findings = analysis.report.findings
|
|
.where((finding) => finding.rule == 'dead-handler')
|
|
.toList();
|
|
expect(findings, isNotEmpty, reason: 'the fixture has dead handlers');
|
|
|
|
for (final finding in findings) {
|
|
expect(
|
|
finding.message,
|
|
isNot(contains('anywhere')),
|
|
reason: 'never claim more than the scanned scope supports',
|
|
);
|
|
expect(finding.message, contains(project.directories.join(', ')));
|
|
}
|
|
|
|
expect(
|
|
findings.first.remediation,
|
|
contains('--dir'),
|
|
reason:
|
|
'the reader should be told how to widen the scan before wiring',
|
|
);
|
|
} finally {
|
|
await analysis.dispose();
|
|
}
|
|
},
|
|
);
|
|
|
|
test('the scanned directories are recorded on the project', () async {
|
|
final defaulted = await loadProject('test/fixtures/stuck_loading');
|
|
expect(defaulted.directories, const ['lib']);
|
|
|
|
// The fixture has no test/ directory, and a --dir that does not exist is
|
|
// now an error rather than skipped, so widen a copy that has one.
|
|
final temp = Directory.systemTemp.createTempSync('pipe-widened');
|
|
Directory('${temp.path}/.dart_tool').createSync();
|
|
File('${temp.path}/.dart_tool/package_config.json').writeAsStringSync(
|
|
File(
|
|
'test/fixtures/stuck_loading/.dart_tool/package_config.json',
|
|
).readAsStringSync(),
|
|
);
|
|
File('${temp.path}/lib/a.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('class A {}\n');
|
|
File('${temp.path}/test/a_test.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('void main() {}\n');
|
|
final widened = await loadProject(
|
|
temp.path,
|
|
directories: const ['lib', 'test'],
|
|
);
|
|
expect(widened.directories, const ['lib', 'test']);
|
|
});
|
|
|
|
test('scanning test/ as well changes the claim set', () async {
|
|
// The mechanism behind the overclaim: the same project yields different
|
|
// dead-handler findings depending on scope, so the message must name it.
|
|
final report = await analyzeProject(
|
|
await loadProject('test/fixtures/stuck_loading'),
|
|
concurrency: 1,
|
|
);
|
|
try {
|
|
expect(jsonEncode(report.project.directories), jsonEncode(const ['lib']));
|
|
} finally {
|
|
await report.dispose();
|
|
}
|
|
});
|
|
}
|