import 'dart:io'; import 'package:pipe/pipe.dart'; import 'package:test/test.dart'; /// The fixture plus one file whose references do not resolve: it constructs /// `NeverDispatched` without importing it, and imports a file that is missing. String _corpusWithUnresolvedFile() { final from = Directory('test/fixtures/stuck_loading'); final to = Directory.systemTemp.createTempSync('pipe-unresolved'); for (final entity in from.listSync(recursive: true)) { final relative = entity.path.substring(from.path.length + 1); final target = '${to.path}/$relative'; if (entity is Directory) { Directory(target).createSync(recursive: true); } else if (entity is File) { File(target) ..createSync(recursive: true) ..writeAsBytesSync(entity.readAsBytesSync()); } } File('${to.path}/lib/unresolved_ui.dart').writeAsStringSync(''' import 'widgets_that_do_not_exist.dart'; // Unresolved, `NeverDispatched()` parses as a method call rather than a // construction, so the dispatch below is invisible to the analysis. void tapNever(dynamic bloc) => bloc.add(NeverDispatched()); '''); return to.path; } void main() { test( 'a finding an unresolved file could refute is not high confidence', () async { // Every pipe finding is a whole-program absence claim, and an unresolved // reference anywhere can hide the evidence that refutes it. On a real app // whose package:flutter could not be read (40k analyzer errors), check // reported findings at high confidence with no warning at all. final analysis = await analyzeProject( await loadProject(_corpusWithUnresolvedFile()), concurrency: 1, ); try { final findings = analysis.report.findings; final dead = findings.singleWhere( (f) => f.rule == ruleDeadHandler && f.data['event'] == 'NeverDispatched', ); expect(dead.confidence, 'medium'); expect(dead.data['confidenceLowered'], 'unresolved-references'); // The unresolved file never mentions UnhandledTap, so it cannot hide // evidence about it: one stray error must not bury every finding. final untouched = findings.singleWhere( (f) => f.rule == ruleEventWithoutHandler && f.data['event'] == 'UnhandledTap', ); expect(untouched.confidence, 'high'); expect(untouched.data.containsKey('confidenceLowered'), isFalse); expect( analysis.report.notes.join('\n'), allOf( contains('unresolved references'), contains('unresolved_ui.dart'), contains('1 finding(s)'), ), ); } finally { await analysis.dispose(); } }, ); test('a fully resolved project keeps its confidence', () async { final analysis = await analyzeProject( await loadProject('test/fixtures/stuck_loading'), concurrency: 1, ); try { expect( analysis.report.findings.where((f) => f.confidence == 'high'), isNotEmpty, ); expect( analysis.report.findings.where( (f) => f.data.containsKey('confidenceLowered'), ), isEmpty, ); } finally { await analysis.dispose(); } }); }