Unverified Commit 4c99da6c authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Avoid printing blank lines between "Another exception was thrown:" messages. (#119587)

parent 8f5949ed
......@@ -169,8 +169,9 @@ Future<void> run(List<String> arguments) async {
// Try with the --watch analyzer, to make sure it returns success also.
// The --benchmark argument exits after one run.
// We specify a failureMessage so that the actual output is muted in the case where _runFlutterAnalyze above already failed.
printProgress('Dart analysis (with --watch)...');
await _runFlutterAnalyze(flutterRoot, options: <String>[
await _runFlutterAnalyze(flutterRoot, failureMessage: 'Dart analyzer failed when --watch was used.', options: <String>[
'--flutter-repo',
'--watch',
'--benchmark',
......@@ -196,7 +197,7 @@ Future<void> run(List<String> arguments) async {
],
workingDirectory: flutterRoot,
);
await _runFlutterAnalyze(outDir.path, options: <String>[
await _runFlutterAnalyze(outDir.path, failureMessage: 'Dart analyzer failed on mega_gallery benchmark.', options: <String>[
'--watch',
'--benchmark',
...arguments,
......@@ -1942,11 +1943,13 @@ Diff at character #$indexOfDifference
Future<CommandResult> _runFlutterAnalyze(String workingDirectory, {
List<String> options = const <String>[],
String? failureMessage,
}) async {
return runCommand(
flutter,
<String>['analyze', ...options],
workingDirectory: workingDirectory,
failureMessage: failureMessage,
);
}
......
......@@ -190,13 +190,24 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
print(result.flattenedStderr);
break;
}
String allOutput;
if (failureMessage == null) {
allOutput = '${result.flattenedStdout}\n${result.flattenedStderr}';
if (allOutput.split('\n').length > 10) {
allOutput = '(stdout/stderr output was more than 10 lines)';
}
} else {
allOutput = '';
}
foundError(<String>[
if (failureMessage != null)
failureMessage
else
'$bold${red}Command exited with exit code ${result.exitCode} but expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'} exit code.$reset',
failureMessage,
'${bold}Command: $green$commandDescription$reset',
'${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
if (failureMessage == null)
'$bold${red}Command exited with exit code ${result.exitCode} but expected ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'} exit code.$reset',
'${bold}Working directory: $cyan${path.absolute(relativeWorkingDir)}$reset',
if (allOutput.isNotEmpty)
'${bold}stdout and stderr output:\n$allOutput',
]);
} else {
print('ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
......
......@@ -82,8 +82,7 @@ VoidCallback? onError;
bool get hasError => _hasError;
bool _hasError = false;
Iterable<String> get errorMessages => _errorMessages;
List<String> _errorMessages = <String>[];
List<List<String>> _errorMessages = <List<String>>[];
final List<String> _pendingLogs = <String>[];
Timer? _hideTimer; // When this is null, the output is verbose.
......@@ -104,7 +103,7 @@ void foundError(List<String> messages) {
// another error.
_pendingLogs.forEach(_printLoudly);
_pendingLogs.clear();
_errorMessages.addAll(messages);
_errorMessages.add(messages);
_hasError = true;
if (onError != null) {
onError!();
......@@ -124,8 +123,18 @@ Never reportErrorsAndExit() {
_hideTimer?.cancel();
_hideTimer = null;
print(redLine);
print('For your convenience, the error messages reported above are repeated here:');
_errorMessages.forEach(print);
print('${red}For your convenience, the error messages reported above are repeated here:$reset');
final bool printSeparators = _errorMessages.any((List<String> messages) => messages.length > 1);
if (printSeparators) {
print(' 🙙 🙛 ');
}
for (int index = 0; index < _errorMessages.length * 2 - 1; index += 1) {
if (index.isEven) {
_errorMessages[index ~/ 2].forEach(print);
} else if (printSeparators) {
print(' 🙙 🙛 ');
}
}
print(redLine);
system.exit(1);
}
......
......@@ -1329,9 +1329,25 @@ abstract class ResidentRunner extends ResidentHandlers {
void printStructuredErrorLog(vm_service.Event event) {
if (event.extensionKind == 'Flutter.Error' && !machine) {
final Map<dynamic, dynamic>? json = event.extensionData?.data;
final Map<String, Object?>? json = event.extensionData?.data;
if (json != null && json.containsKey('renderedErrorText')) {
globals.printStatus('\n${json['renderedErrorText']}');
final int errorsSinceReload;
if (json.containsKey('errorsSinceReload') && json['errorsSinceReload'] is int) {
errorsSinceReload = json['errorsSinceReload']! as int;
} else {
errorsSinceReload = 0;
}
if (errorsSinceReload == 0) {
// We print a blank line around the first error, to more clearly emphasize it
// in the output. (Other errors don't get this.)
globals.printStatus('');
}
globals.printStatus('${json['renderedErrorText']}');
if (errorsSinceReload == 0) {
globals.printStatus('');
}
} else {
globals.printError('Received an invalid ${globals.logger.terminal.bolden("Flutter.Error")} message from app: $json');
}
}
}
......
......@@ -285,11 +285,11 @@ Future<vm_service.VmService> setUpVmService(
}
if (printStructuredErrorLogMethod != null) {
vmService.onExtensionEvent.listen(printStructuredErrorLogMethod);
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
registrationRequests.add(vmService
.streamListen(vm_service.EventStreams.kExtension)
.then<vm_service.Success?>((vm_service.Success success) => success)
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
.catchError((Object? error) => null, test: (Object? error) => error is vm_service.RPCError)
);
}
......
......@@ -429,49 +429,105 @@ void main() {
() async {
final ResidentRunner residentWebRunner =
setUpResidentRunner(flutterDevice, logger: testLogger);
final Map<String, String> extensionData = <String, String>{
'test': 'data',
'renderedErrorText': 'error text',
};
final Map<String, String> emptyExtensionData = <String, String>{
'test': 'data',
'renderedErrorText': '',
};
final Map<String, String> nonStructuredErrorData = <String, String>{
'other': 'other stuff',
};
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
final List<VmServiceExpectation> requests = <VmServiceExpectation>[
...kAttachExpectations,
// This is the first error message of a session.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(extensionData),
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'errorsSinceReload': 0,
'renderedErrorText': 'first',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// Empty error text should not break anything.
// This is the second error message of a session.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(emptyExtensionData),
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'errorsSinceReload': 1,
'renderedErrorText': 'second',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// This is not Flutter.Error kind data, so it should not be logged.
// This is not Flutter.Error kind data, so it should not be logged, even though it has a renderedErrorText field.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Other',
extensionData: vm_service.ExtensionData.parse(nonStructuredErrorData),
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'errorsSinceReload': 2,
'renderedErrorText': 'not an error',
}),
kind: vm_service.EventStreams.kExtension,
),
),
]);
// This is the third error message of a session.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'errorsSinceReload': 2,
'renderedErrorText': 'third',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// This is bogus error data.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'other': 'bad stuff',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// Empty error text should not break anything.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'test': 'data',
'renderedErrorText': '',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// Messages without errorsSinceReload should act as if errorsSinceReload: 0
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
'test': 'data',
'renderedErrorText': 'error text',
}),
kind: vm_service.EventStreams.kExtension,
),
),
// When adding things here, make sure the last one is supposed to output something
// to the statusLog, otherwise you won't be able to distinguish the absence of output
// due to it passing the test from absence due to it not running the test.
];
// We use requests below, so make a copy of it here (FakeVmServiceHost will
// clear its copy internally, which would affect our pumping below).
fakeVmServiceHost = FakeVmServiceHost(requests: requests.toList());
setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter =
......@@ -480,10 +536,34 @@ void main() {
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await null;
assert(requests.length > 5, 'requests was modified');
for (final Object _ in requests) {
// pump the task queue once for each message
await null;
}
expect(testLogger.statusText, contains('\nerror text'));
expect(testLogger.statusText, isNot(contains('other stuff')));
expect(testLogger.statusText,
'Launching lib/main.dart on FakeDevice in debug mode...\n'
'Waiting for connection from debug service on FakeDevice...\n'
'Debug service listening on ws://127.0.0.1/abcd/\n'
'\n'
'💪 Running with sound null safety 💪\n'
'\n'
'first\n'
'\n'
'second\n'
'third\n'
'\n'
'\n' // the empty message
'\n'
'\n'
'error text\n'
'\n'
);
expect(testLogger.errorText,
'Received an invalid Flutter.Error message from app: {other: bad stuff}\n'
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
......
......@@ -548,6 +548,7 @@ void main() {
' verticalDirection: down',
'◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤',
'════════════════════════════════════════════════════════════════════════════════════════════════════',
'',
startsWith('Reloaded 0 libraries in '),
'',
'Application finished.',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment