analyzer_testing: split assertDiagnosticsIn into multiple, overridable methods

Work towards https://github.com/dart-lang/sdk/issues/61271

Change-Id: I3732176b84fb0ce79d2de180e8fb32d76ea4a08c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/444361
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Sam Rawlins
2025-08-11 09:47:30 -07:00
committed by Commit Queue
parent ca74ec517a
commit 69e9fd545f
3 changed files with 126 additions and 78 deletions
+4
View File
@@ -5,8 +5,10 @@ package:analyzer_testing/analysis_rule/analysis_rule.dart:
analysisRule (getter: String)
assertNoPubspecDiagnostics (method: Future<void> Function(String))
assertPubspecDiagnostics (method: Future<void> Function(String, List<ExpectedDiagnostic>))
correctionMessage (method: String Function(List<Diagnostic>))
lint (method: ExpectedDiagnostic Function(int, int, {Pattern? correctionContains, Pattern? messageContains, String? name}))
setUp (method: void Function())
unexpectedMessage (method: String Function(List<Diagnostic>))
package:analyzer_testing/experiments/experiments.dart:
experimentsForTests (static getter: List<String>)
experimentsForTests= (static setter: List<String>)
@@ -78,6 +80,8 @@ dart:core:
int (referenced)
package:_fe_analyzer_shared/src/base/errors.dart:
DiagnosticCode (referenced)
package:analyzer/diagnostic/diagnostic.dart:
Diagnostic (referenced)
package:analyzer/file_system/file_system.dart:
File (referenced)
Folder (referenced)
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert' show json;
import 'package:analyzer/analysis_rule/pubspec.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
@@ -46,6 +48,28 @@ abstract class AnalysisRuleTest extends PubPackageResolutionTest {
assertDiagnosticsIn(errors, expectedDiagnostics);
}
@override
String correctionMessage(List<Diagnostic> diagnostics) {
var buffer = StringBuffer();
diagnostics.sort((first, second) => first.offset.compareTo(second.offset));
buffer.writeln();
buffer.writeln('To accept the current state, expect:');
for (var actual in diagnostics) {
if (actual.diagnosticCode is LintCode) {
buffer.write(' lint(');
} else {
buffer.write(' error(${actual.diagnosticCode}, ');
}
buffer.write('${actual.offset}, ${actual.length}');
if (actual.diagnosticCode.name != analysisRule) {
buffer.write(", name: '${actual.diagnosticCode.name}'");
}
buffer.writeln('),');
}
return buffer.toString();
}
/// Returns an "expected diagnostic" for [analysisRule] (or [name], if given)
/// at [offset] and [length].
///
@@ -79,6 +103,25 @@ abstract class AnalysisRuleTest extends PubPackageResolutionTest {
);
}
@override
String unexpectedMessage(List<Diagnostic> unmatchedActual) {
var buffer = StringBuffer();
if (buffer.isNotEmpty) {
buffer.writeln();
}
buffer.writeln('Found but did not expect:');
for (var actual in unmatchedActual) {
buffer.write(' $analysisRule.${actual.diagnosticCode.name} [');
buffer.write('${actual.offset}, ${actual.length}, ${actual.message}');
if (actual.correctionMessage case Pattern correctionMessage) {
buffer.write(', ');
buffer.write(json.encode(correctionMessage));
}
buffer.writeln(']');
}
return buffer.toString();
}
Future<List<Diagnostic>> _analyzePubspecFile(String content) async {
var path = convertPath(testPackagePubspecPath);
var pubspecRules = <AbstractAnalysisRule, PubspecVisitor<Object?>>{};
@@ -212,9 +212,7 @@ class PubPackageResolutionTest with MockPackagesMixin, ResourceProviderMixin {
List<Diagnostic> diagnostics,
List<ExpectedDiagnostic> expectedDiagnostics,
) {
//
// Match actual diagnostics to expected diagnostics.
//
var unmatchedActual = diagnostics.toList();
var unmatchedExpected = expectedDiagnostics.toList();
var actualIndex = 0;
@@ -236,99 +234,33 @@ class PubPackageResolutionTest with MockPackagesMixin, ResourceProviderMixin {
actualIndex++;
}
}
//
// Write the results.
//
// Print the results to the terminal.
var buffer = StringBuffer();
if (unmatchedExpected.isNotEmpty) {
buffer.writeln('Expected but did not find:');
for (var expected in unmatchedExpected) {
buffer.write(' ');
if (expected is ExpectedError) {
buffer.write(expected._code);
}
if (expected is ExpectedLint) {
buffer.write(expected._lintName);
}
buffer.write(' [');
buffer.write(expected._offset);
buffer.write(', ');
buffer.write(expected._length);
if (expected._messageContains != null) {
buffer.write(', messageContains: ');
buffer.write(json.encode(expected._messageContains.toString()));
}
if (expected._correctionContains != null) {
buffer.write(', correctionContains: ');
buffer.write(json.encode(expected._correctionContains.toString()));
}
buffer.writeln(']');
}
buffer.write(missingExpectedMessage(unmatchedExpected));
}
if (unmatchedActual.isNotEmpty) {
if (buffer.isNotEmpty) {
buffer.writeln();
}
buffer.writeln('Found but did not expect:');
for (var actual in unmatchedActual) {
buffer.write(' ');
buffer.write(actual.diagnosticCode);
buffer.write(' [');
buffer.write(actual.offset);
buffer.write(', ');
buffer.write(actual.length);
buffer.write(', ');
buffer.write(actual.message);
if (actual.correctionMessage != null) {
buffer.write(', ');
buffer.write(json.encode(actual.correctionMessage));
}
buffer.writeln(']');
}
buffer.write(unexpectedMessage(unmatchedActual));
}
if (buffer.isNotEmpty) {
diagnostics.sort(
(first, second) => first.offset.compareTo(second.offset),
);
buffer.writeln();
buffer.writeln('To accept the current state, expect:');
for (var actual in diagnostics) {
late String diagnosticKind;
Object? description;
if (actual.diagnosticCode is LintCode) {
diagnosticKind = 'lint';
} else {
diagnosticKind = 'error';
description = actual.diagnosticCode;
}
buffer.write(' $diagnosticKind(');
if (description != null) {
buffer.write(description);
buffer.write(', ');
}
buffer.write(actual.offset);
buffer.write(', ');
buffer.write(actual.length);
buffer.writeln('),');
}
if (unmatchedExpected.isNotEmpty || unmatchedActual.isNotEmpty) {
buffer.write(correctionMessage(diagnostics));
if (dumpAstOnFailures) {
buffer.writeln();
buffer.writeln();
try {
var astSink = StringBuffer();
try {
Spelunker(
result.unit.toSource(),
sink: astSink,
sink: buffer,
featureSet: result.unit.featureSet,
).spelunk();
buffer.write(astSink);
buffer.writeln();
// I hereby choose to catch this type.
} on ArgumentError catch (_) {
// Perhaps we encountered a parsing error while spelunking.
}
buffer.writeln();
}
fail(buffer.toString());
@@ -371,6 +303,54 @@ class PubPackageResolutionTest with MockPackagesMixin, ResourceProviderMixin {
Future<void> assertNoDiagnosticsInFile(String path) async =>
assertDiagnosticsInFile(path, const []);
/// Text to display upon failure, which indicates possible corrections.
@visibleForOverriding
String correctionMessage(List<Diagnostic> diagnostics) {
var buffer = StringBuffer();
diagnostics.sort((first, second) => first.offset.compareTo(second.offset));
buffer.writeln();
buffer.writeln('To accept the current state, expect:');
for (var actual in diagnostics) {
if (actual.diagnosticCode is LintCode) {
buffer.write(' lint(');
} else {
buffer.write(' error(${actual.diagnosticCode}, ');
}
buffer.write('${actual.offset}, ${actual.length}),');
}
return buffer.toString();
}
/// Text to display upon failure, indicating that [unmatchedExpected]
/// diagnostics were expected, but not found.
@visibleForOverriding
String missingExpectedMessage(List<ExpectedDiagnostic> unmatchedExpected) {
var buffer = StringBuffer();
buffer.writeln('Expected but did not find:');
for (var expected in unmatchedExpected) {
buffer.write(' ');
if (expected is ExpectedError) {
buffer.write(expected._code);
}
if (expected is ExpectedLint) {
buffer.write(expected._lintName);
}
buffer.write(' [${expected._offset}, ');
buffer.write(expected._length);
if (expected._messageContains case Pattern messageContains) {
buffer.write(', messageContains: ');
buffer.write(json.encode(messageContains.toString()));
}
if (expected._correctionContains case Pattern correctionContains) {
buffer.write(', correctionContains: ');
buffer.write(json.encode(correctionContains.toString()));
}
buffer.writeln(']');
}
return buffer.toString();
}
@override
File newFile(String path, String content) {
if (_analysisContextCollection != null && !path.endsWith('.dart')) {
@@ -414,6 +394,27 @@ class PubPackageResolutionTest with MockPackagesMixin, ResourceProviderMixin {
_analysisContextCollection = null;
}
/// Text to display upon failure, indicating that [unmatchedActual]
/// diagnostics were found, but unexpected.
@visibleForOverriding
String unexpectedMessage(List<Diagnostic> unmatchedActual) {
var buffer = StringBuffer();
if (buffer.isNotEmpty) {
buffer.writeln();
}
buffer.writeln('Found but did not expect:');
for (var actual in unmatchedActual) {
buffer.write(' ${actual.diagnosticCode} [');
buffer.write('${actual.offset}, ${actual.length}, ${actual.message}');
if (actual.correctionMessage case Pattern correctionMessage) {
buffer.write(', ');
buffer.write(json.encode(correctionMessage));
}
buffer.writeln(']');
}
return buffer.toString();
}
void writePackageConfig(String path, PackageConfigFileBuilder config) {
newFile(path, config.toContent(pathContext: pathContext));
}