Files
sdk/pkg/testing/lib/src/log.dart
T
Paul Berry 3186c708a2 [testing] Migrate to new constructor decl syntax.
(Part of https://github.com/dart-lang/sdk/issues/63288)

This change migrates the testing package to use the new constructor
declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
package's SDK constraint to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Change-Id: I01dab822f08d804d5c7ce997dee6d7866a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508427
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
2026-06-01 18:04:45 -07:00

295 lines
6.8 KiB
Dart

// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// 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.
library;
import 'dart:io' show stdout;
import 'chain.dart' show Result, Step;
import 'suite.dart' show Suite;
import 'test_description.dart' show TestDescription;
import 'expectation.dart' show Expectation;
/// ANSI escape code for moving cursor one line up.
/// See [CSI codes](https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes).
const String cursorUpCodes = "\u001b[1A";
/// ANSI escape code for erasing the entire line.
/// See [CSI codes](https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes).
const String eraseLineCodes = "\u001b[2K";
final bool enableAnsiEscapes = stdout.supportsAnsiEscapes;
final String cursorUp = enableAnsiEscapes ? cursorUpCodes : "";
final String eraseLine = enableAnsiEscapes ? eraseLineCodes : "";
final Stopwatch wallclock = Stopwatch()..start();
bool _isVerbose = const bool.fromEnvironment("verbose");
bool get isVerbose => _isVerbose;
void enableVerboseOutput() {
_isVerbose = true;
}
abstract class Logger {
void logTestStart(
int completed,
int failed,
int total,
Suite suite,
TestDescription description,
);
void logTestComplete(
int completed,
int failed,
int total,
Suite suite,
TestDescription description,
);
void logStepStart(
int completed,
int failed,
int total,
Suite suite,
TestDescription description,
Step step,
);
void logStepComplete(
int completed,
int failed,
int total,
Suite suite,
TestDescription description,
Step step,
);
void logProgress(String message);
void logMessage(Object message);
void logNumberedLines(String text);
void logExpectedResult(
Suite suite,
TestDescription description,
Result result,
Set<Expectation> expectedOutcomes,
);
void logUnexpectedResult(
Suite suite,
TestDescription description,
Result result,
Set<Expectation> expectedOutcomes,
);
void logSuiteStarted(Suite suite);
void logSuiteComplete(Suite suite);
void logUncaughtError(Object error, StackTrace stackTrace);
/// Issued when there's been a crash caught by the framework.
/// Notice that the exit-code has already been set and that the error has
/// been printed to stderr.
void noticeFrameworkCatchError(Object? error, StackTrace stackTrace);
}
class StdoutLogger implements Logger {
const new();
@override
void logTestStart(
int completed,
int failed,
int total,
Suite? suite,
TestDescription? description,
) {}
@override
void logTestComplete(
int completed,
int failed,
int total,
Suite? suite,
TestDescription? description,
) {
String message = formatProgress(completed, failed, total);
if (suite != null) {
message += ": ${formatTestDescription(suite, description!)}";
}
logProgress(message);
}
@override
void logStepStart(
int completed,
int failed,
int total,
Suite? suite,
TestDescription description,
Step step,
) {
String message = formatProgress(completed, failed, total);
if (suite != null) {
message += ": ${formatTestDescription(suite, description)} ${step.name}";
if (step.isAsync) {
message += "...";
}
}
logProgress(message);
}
@override
void logStepComplete(
int completed,
int failed,
int total,
Suite? suite,
TestDescription description,
Step step,
) {
if (!step.isAsync) return;
String message = formatProgress(completed, failed, total);
if (suite != null) {
message += ": ${formatTestDescription(suite, description)} ${step.name}!";
}
logProgress(message);
}
@override
void logProgress(String message) {
if (isVerbose) {
print(message);
} else {
print("$eraseLine$message$cursorUp");
}
}
String formatProgress(int completed, int failed, int total) {
Duration elapsed = wallclock.elapsed;
String percent = pad((completed / total * 100.0).toStringAsFixed(1), 5);
String good = pad(completed - failed, 5);
String bad = pad(failed, 5);
String minutes = pad(elapsed.inMinutes, 2, filler: "0");
String seconds = pad(elapsed.inSeconds % 60, 2, filler: "0");
return "[ $minutes:$seconds | $percent% | +$good | -$bad ]";
}
String formatTestDescription(Suite suite, TestDescription description) {
return "${suite.name}/${description.shortName}";
}
@override
void logMessage(Object message) {
if (isVerbose) {
print("$message");
}
}
@override
void logNumberedLines(String text) {
if (isVerbose) {
print(numberedLines(text));
}
}
@override
void logExpectedResult(
Suite suite,
TestDescription description,
Result result,
Set<Expectation> expectedOutcomes,
) {}
@override
void logUnexpectedResult(
Suite suite,
TestDescription description,
Result result,
Set<Expectation> expectedOutcomes,
) {
print("${eraseLine}UNEXPECTED: ${suite.name}/${description.shortName}");
Uri? statusFile = suite.statusFile;
if (statusFile != null) {
String path = statusFile.toFilePath();
if (result.outcome == Expectation.pass) {
print("The test unexpectedly passed, please update $path.");
} else {
print(
"The test had the outcome ${result.outcome}, but the status file "
"($path) allows these outcomes: ${expectedOutcomes.join(' ')}",
);
}
}
String log = result.log;
if (log.isNotEmpty) {
print(log);
}
if (result.error != null) {
print(result.error);
if (result.trace != null) {
print(result.trace);
}
}
}
@override
void logSuiteStarted(Suite suite) {
print("Running suite ${suite.name}...");
}
@override
void logSuiteComplete(Suite suite) {
if (!isVerbose) {
print("");
}
}
@override
void logUncaughtError(Object error, StackTrace stackTrace) {
logMessage(error);
logMessage(stackTrace);
}
@override
void noticeFrameworkCatchError(error, StackTrace stackTrace) {}
}
String pad(Object o, int pad, {String filler = " "}) {
String result = "${filler * pad}$o";
return result.substring(result.length - pad);
}
String numberedLines(String text) {
StringBuffer result = StringBuffer();
int lineNumber = 1;
List<String> lines = splitLines(text);
int pad = "${lines.length}".length;
String fill = " " * pad;
for (String line in lines) {
String paddedLineNumber = "$fill$lineNumber";
paddedLineNumber = paddedLineNumber.substring(
paddedLineNumber.length - pad,
);
result.write("$paddedLineNumber: $line");
lineNumber++;
}
return '$result';
}
List<String> splitLines(String text) {
return text.split(RegExp('^', multiLine: true));
}