The committed versions were not in canonical format; they were reformatted after the commit, which would have made the pre-commit hook rewrite them on the next commit and block it.
191 lines
6.1 KiB
Dart
191 lines
6.1 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:pipe/pipe.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
late String manifestPath;
|
|
|
|
setUpAll(() {
|
|
// Point the manifest at the fixture project so recall machinery is covered
|
|
// in CI without depending on external repos.
|
|
final fixture = Directory('test/fixtures/stuck_loading').absolute.path;
|
|
final temp = Directory.systemTemp.createTempSync('pipe-recall');
|
|
manifestPath = '${temp.path}/manifest.json';
|
|
File(manifestPath).writeAsStringSync(
|
|
const JsonEncoder.withIndent(' ').convert({
|
|
'version': 1,
|
|
'cases': [
|
|
{
|
|
'id': 'known-real-bug',
|
|
'repo': fixture,
|
|
'rule': 'emitted-not-rendered',
|
|
'match': {'union': 'ItemState'},
|
|
},
|
|
{
|
|
'id': 'bug-that-does-not-exist',
|
|
'repo': fixture,
|
|
'rule': 'emitted-not-rendered',
|
|
'match': {'union': 'NoSuchState'},
|
|
'note': 'expected on master; the fix lives on an unmerged branch',
|
|
},
|
|
{
|
|
'id': 'repo-absent-on-this-machine',
|
|
'repo': 'definitely-not-a-real-repo',
|
|
'rule': 'emitted-not-rendered',
|
|
'match': {'union': 'ItemState'},
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('reports found, missed and skipped separately', () async {
|
|
final report = await runRecall(
|
|
manifestPath: manifestPath,
|
|
root: Directory.current.path,
|
|
concurrency: 1,
|
|
);
|
|
|
|
expect(report.outcomes, hasLength(3));
|
|
expect(report.evaluated, hasLength(2));
|
|
expect(report.skippedCases, hasLength(1));
|
|
|
|
final found = report.evaluated.where((o) => o.found).toList();
|
|
expect(found, hasLength(1));
|
|
expect(found.single.testCase.id, 'known-real-bug');
|
|
expect(found.single.at, contains('item_bloc.dart'));
|
|
expect(found.single.at, contains(':'), reason: 'should be file:line');
|
|
|
|
expect(report.missed, hasLength(1));
|
|
expect(report.missed.single.testCase.id, 'bug-that-does-not-exist');
|
|
expect(report.allEvaluatedFound, isFalse);
|
|
});
|
|
|
|
test('per-rule recall is found/total over evaluated cases only', () async {
|
|
final report = await runRecall(
|
|
manifestPath: manifestPath,
|
|
root: Directory.current.path,
|
|
concurrency: 1,
|
|
);
|
|
// The skipped case must not dilute recall.
|
|
expect(report.recallByRule, {'emitted-not-rendered': '1/2'});
|
|
expect(report.toJson()['skipped'], 1);
|
|
expect(report.toJson()['evaluated'], 2);
|
|
});
|
|
|
|
test('a missing manifest is an actionable error, not a crash', () {
|
|
expect(
|
|
() => loadRecallCases('/nope/does-not-exist.json'),
|
|
throwsA(isA<PipeException>()),
|
|
);
|
|
});
|
|
|
|
test('a malformed manifest is rejected', () {
|
|
final temp = Directory.systemTemp.createTempSync('pipe-recall-bad');
|
|
final bad = '${temp.path}/bad.json';
|
|
File(bad).writeAsStringSync('{"cases": [{"id": "x"}]}');
|
|
expect(() => loadRecallCases(bad), throwsA(isA<PipeException>()));
|
|
});
|
|
|
|
test(
|
|
'a MISSED case prints its note, so an expected miss explains itself',
|
|
() async {
|
|
// Without this, the manifest note never reached the reader: a case that
|
|
// misses because its fix is on an unmerged branch looked exactly like a
|
|
// regression in pipe.
|
|
final report = await runRecall(
|
|
manifestPath: manifestPath,
|
|
root: Directory.current.path,
|
|
concurrency: 1,
|
|
);
|
|
|
|
final text = report.describe();
|
|
expect(text, contains('MISSED'));
|
|
expect(
|
|
text,
|
|
contains(
|
|
'note: expected on master; the fix lives on an unmerged branch',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
test('an unknown status is rejected instead of silently read as open', () {
|
|
// 'fix' is the typo a reader is most likely to write. Defaulting it to
|
|
// `open` reported a MISSED case and sent them hunting for a regression that
|
|
// was really a manifest typo.
|
|
final temp = Directory.systemTemp.createTempSync('pipe-recall-status');
|
|
final bad = '${temp.path}/status.json';
|
|
File(bad).writeAsStringSync(
|
|
const JsonEncoder.withIndent(' ').convert({
|
|
'version': 1,
|
|
'cases': [
|
|
{
|
|
'id': 'typo-status',
|
|
'repo': 'test/fixtures/stuck_loading',
|
|
'rule': 'emitted-not-rendered',
|
|
'match': {'union': 'ItemState'},
|
|
'status': 'fix',
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
expect(
|
|
() => loadRecallCases(bad),
|
|
throwsA(
|
|
isA<PipeException>().having(
|
|
(e) => e.toString(),
|
|
'message',
|
|
allOf(contains('status "fix"'), contains('open'), contains('fixed')),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('a vacuous recall run fails instead of reporting 0/0 as success', () async {
|
|
// "0/0 known bugs accounted for" reads as a pass, so both ways of evaluating
|
|
// nothing are now failures: an empty manifest, and a --root under which none
|
|
// of the manifest's repos exist.
|
|
final temp = Directory.systemTemp.createTempSync('pipe-recall-vacuous');
|
|
final emptyManifest = '${temp.path}/empty.json';
|
|
File(emptyManifest).writeAsStringSync('{"version": 1, "cases": []}');
|
|
await expectLater(
|
|
runRecall(
|
|
manifestPath: emptyManifest,
|
|
root: Directory.current.path,
|
|
concurrency: 1,
|
|
),
|
|
throwsA(isA<PipeException>()),
|
|
);
|
|
|
|
// A case whose repo is a RELATIVE name that does not resolve under --root is
|
|
// skipped; when that is all of them, the run verified nothing.
|
|
final absentManifest = '${temp.path}/absent.json';
|
|
File(absentManifest).writeAsStringSync(
|
|
const JsonEncoder.withIndent(' ').convert({
|
|
'version': 1,
|
|
'cases': [
|
|
{
|
|
'id': 'absent-repo',
|
|
'repo': 'definitely-not-a-real-repo',
|
|
'rule': 'dead-handler',
|
|
'match': {'event': 'NeverDispatched'},
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
await expectLater(
|
|
runRecall(manifestPath: absentManifest, root: temp.path, concurrency: 1),
|
|
throwsA(
|
|
isA<PipeException>().having(
|
|
(error) => error.toString(),
|
|
'message',
|
|
contains('evaluated none'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|