Index: pkg/testing/lib/src/log.dart |
diff --git a/pkg/testing/lib/src/log.dart b/pkg/testing/lib/src/log.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..64c49fa474ca150b3401af7d0c50344a083dad2c |
--- /dev/null |
+++ b/pkg/testing/lib/src/log.dart |
@@ -0,0 +1,164 @@ |
+// 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.md file. |
+ |
+library testing.log; |
+ |
+import 'chain.dart' show |
+ Result, |
+ Step; |
+ |
+import 'suite.dart' show |
+ Suite; |
+ |
+import 'test_description.dart' show |
+ TestDescription; |
+ |
+import 'test_dart/status_file_parser.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 cursorUp = "\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 eraseLine = "\u001b[2K"; |
+ |
+final Stopwatch wallclock = new Stopwatch()..start(); |
+ |
+bool _isVerbose = const bool.fromEnvironment("verbose"); |
+ |
+bool get isVerbose => _isVerbose; |
+ |
+void enableVerboseOutput() { |
+ _isVerbose = true; |
+} |
+ |
+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); |
+} |
+ |
+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); |
+} |
+ |
+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); |
+} |
+ |
+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, 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}"; |
+} |
+ |
+void logMessage(Object message) { |
+ if (isVerbose) { |
+ print("$message"); |
+ } |
+} |
+ |
+void logNumberedLines(String text) { |
+ if (isVerbose) { |
+ print(numberedLines(text)); |
+ } |
+} |
+ |
+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); |
+ } |
+ } |
+} |
+ |
+void logSuiteComplete() { |
+ if (!isVerbose) { |
+ print(""); |
+ } |
+} |
+ |
+void logUncaughtError(error, StackTrace stackTrace) { |
+ logMessage(error); |
+ if (stackTrace != null) { |
+ logMessage(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 = new 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(new RegExp('^', multiLine: true)); |
+} |