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(); } }); }