Unverified Commit efcd9a80 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Simplify Gradle compiler output. (#21760)

This changes the compiler output for gradle to be less verbose and more easily read.

This only applies to compilation error messages: other gradle messages will continue to print as before.

It also fixes a small problem with the performance measurement printing (see that "7.1s" on it's own line in the original?) so that if something is expected to have multiple lines of output, it prints an initial line, and a "Done" line with the elapsed time, so that it's possible to know what the time applies to.

It also updates the spinner to be fancier, at least on platforms other than Windows (which is missing a lot of symbols in its console font).

Addresses #17307
parent 83cdb573
...@@ -45,6 +45,13 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -45,6 +45,13 @@ class FlutterPlugin implements Plugin<Project> {
private File dynamicProfileFlutterJar private File dynamicProfileFlutterJar
private File dynamicReleaseFlutterJar private File dynamicReleaseFlutterJar
// The name prefix for flutter builds. This is used to identify gradle tasks
// where we expect the flutter tool to provide any error output, and skip the
// standard Gradle error output in the FlutterEventLogger. If you change this,
// be sure to change any instances of this string in symbols in the code below
// to match.
static final String flutterBuildPrefix = "flutterBuild"
private Properties readPropertiesIfExist(File propertiesFile) { private Properties readPropertiesIfExist(File propertiesFile) {
Properties result = new Properties() Properties result = new Properties()
if (propertiesFile.exists()) { if (propertiesFile.exists()) {
...@@ -152,7 +159,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -152,7 +159,7 @@ class FlutterPlugin implements Plugin<Project> {
// Add x86/x86_64 native library. Debug mode only, for now. // Add x86/x86_64 native library. Debug mode only, for now.
flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar") flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
Task flutterX86JarTask = project.tasks.create("flutterBuildX86Jar", Jar) { Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) {
destinationDir flutterX86Jar.parentFile destinationDir flutterX86Jar.parentFile
archiveName flutterX86Jar.name archiveName flutterX86Jar.name
from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") { from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
...@@ -319,7 +326,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -319,7 +326,7 @@ class FlutterPlugin implements Plugin<Project> {
def addFlutterDeps = { variant -> def addFlutterDeps = { variant ->
String flutterBuildMode = buildModeFor(variant.buildType) String flutterBuildMode = buildModeFor(variant.buildType)
if (flutterBuildMode == 'debug' && project.tasks.findByName('flutterBuildX86Jar')) { if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) {
Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac") Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
if (task) { if (task) {
task.dependsOn project.flutterBuildX86Jar task.dependsOn project.flutterBuildX86Jar
...@@ -330,7 +337,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -330,7 +337,7 @@ class FlutterPlugin implements Plugin<Project> {
} }
} }
FlutterTask flutterTask = project.tasks.create(name: "flutterBuild${variant.name.capitalize()}", type: FlutterTask) { FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
flutterRoot this.flutterRoot flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable flutterExecutable this.flutterExecutable
buildMode flutterBuildMode buildMode flutterBuildMode
...@@ -584,3 +591,23 @@ class FlutterTask extends BaseFlutterTask { ...@@ -584,3 +591,23 @@ class FlutterTask extends BaseFlutterTask {
buildBundle() buildBundle()
} }
} }
gradle.useLogger(new FlutterEventLogger())
class FlutterEventLogger extends BuildAdapter implements TaskExecutionListener {
String mostRecentTask = ""
void beforeExecute(Task task) {
mostRecentTask = task.name
}
void afterExecute(Task task, TaskState state) {}
void buildFinished(BuildResult result) {
if (result.failure != null) {
if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.flutterBuildPrefix)) {
result.rethrowFailure()
}
}
}
}
...@@ -299,7 +299,11 @@ Future<Null> buildGradleProject({ ...@@ -299,7 +299,11 @@ Future<Null> buildGradleProject({
Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async { Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async {
// Run 'gradlew build'. // Run 'gradlew build'.
final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true); final Status status = logger.startProgress(
"Running 'gradlew build'...",
expectSlowOperation: true,
multilineOutput: true,
);
final int exitCode = await runCommandAndStreamOutput( final int exitCode = await runCommandAndStreamOutput(
<String>[fs.file(gradle).absolute.path, 'build'], <String>[fs.file(gradle).absolute.path, 'build'],
workingDirectory: project.android.hostAppGradleRoot.path, workingDirectory: project.android.hostAppGradleRoot.path,
...@@ -337,7 +341,11 @@ Future<Null> _buildGradleProjectV2( ...@@ -337,7 +341,11 @@ Future<Null> _buildGradleProjectV2(
throwToolExit('Gradle build aborted.'); throwToolExit('Gradle build aborted.');
} }
} }
final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true); final Status status = logger.startProgress(
"Gradle task '$assembleTask'...",
expectSlowOperation: true,
multilineOutput: true,
);
final String gradlePath = fs.file(gradle).absolute.path; final String gradlePath = fs.file(gradle).absolute.path;
final List<String> command = <String>[gradlePath]; final List<String> command = <String>[gradlePath];
if (logger.isVerbose) { if (logger.isVerbose) {
...@@ -382,7 +390,7 @@ Future<Null> _buildGradleProjectV2( ...@@ -382,7 +390,7 @@ Future<Null> _buildGradleProjectV2(
status.stop(); status.stop();
if (exitCode != 0) if (exitCode != 0)
throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode); throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode);
final File apkFile = _findApkFile(project, buildInfo); final File apkFile = _findApkFile(project, buildInfo);
if (apkFile == null) if (apkFile == null)
......
...@@ -155,6 +155,11 @@ class Stdio { ...@@ -155,6 +155,11 @@ class Stdio {
Stream<List<int>> get stdin => io.stdin; Stream<List<int>> get stdin => io.stdin;
io.IOSink get stdout => io.stdout; io.IOSink get stdout => io.stdout;
io.IOSink get stderr => io.stderr; io.IOSink get stderr => io.stderr;
bool get hasTerminal => io.stdout.hasTerminal;
int get terminalColumns => hasTerminal ? io.stdout.terminalColumns : null;
int get terminalLines => hasTerminal ? io.stdout.terminalLines : null;
bool get supportsAnsiEscapes => hasTerminal ? io.stdout.supportsAnsiEscapes : false;
} }
io.IOSink get stderr => context[Stdio].stderr; io.IOSink get stderr => context[Stdio].stderr;
...@@ -162,3 +167,5 @@ io.IOSink get stderr => context[Stdio].stderr; ...@@ -162,3 +167,5 @@ io.IOSink get stderr => context[Stdio].stderr;
Stream<List<int>> get stdin => context[Stdio].stdin; Stream<List<int>> get stdin => context[Stdio].stdin;
io.IOSink get stdout => context[Stdio].stdout; io.IOSink get stdout => context[Stdio].stdout;
Stdio get stdio => context[Stdio];
...@@ -8,6 +8,7 @@ import 'dart:convert' show LineSplitter; ...@@ -8,6 +8,7 @@ import 'dart:convert' show LineSplitter;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'io.dart'; import 'io.dart';
import 'platform.dart';
import 'terminal.dart'; import 'terminal.dart';
import 'utils.dart'; import 'utils.dart';
...@@ -25,6 +26,8 @@ abstract class Logger { ...@@ -25,6 +26,8 @@ abstract class Logger {
terminal.supportsColor = value; terminal.supportsColor = value;
} }
bool get hasTerminal => stdio.hasTerminal;
/// Display an error level message to the user. Commands should use this if they /// Display an error level message to the user. Commands should use this if they
/// fail in some way. /// fail in some way.
void printError( void printError(
...@@ -62,6 +65,7 @@ abstract class Logger { ...@@ -62,6 +65,7 @@ abstract class Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation, bool expectSlowOperation,
bool multilineOutput,
int progressIndicatorPadding, int progressIndicatorPadding,
}); });
} }
...@@ -129,6 +133,7 @@ class StdoutLogger extends Logger { ...@@ -129,6 +133,7 @@ class StdoutLogger extends Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation, bool expectSlowOperation,
bool multilineOutput,
int progressIndicatorPadding, int progressIndicatorPadding,
}) { }) {
expectSlowOperation ??= false; expectSlowOperation ??= false;
...@@ -141,6 +146,7 @@ class StdoutLogger extends Logger { ...@@ -141,6 +146,7 @@ class StdoutLogger extends Logger {
_status = AnsiStatus( _status = AnsiStatus(
message: message, message: message,
expectSlowOperation: expectSlowOperation, expectSlowOperation: expectSlowOperation,
multilineOutput: multilineOutput,
padding: progressIndicatorPadding, padding: progressIndicatorPadding,
onFinish: _clearStatus, onFinish: _clearStatus,
)..start(); )..start();
...@@ -223,6 +229,7 @@ class BufferLogger extends Logger { ...@@ -223,6 +229,7 @@ class BufferLogger extends Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation, bool expectSlowOperation,
bool multilineOutput,
int progressIndicatorPadding, int progressIndicatorPadding,
}) { }) {
printStatus(message); printStatus(message);
...@@ -280,6 +287,7 @@ class VerboseLogger extends Logger { ...@@ -280,6 +287,7 @@ class VerboseLogger extends Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation, bool expectSlowOperation,
bool multilineOutput,
int progressIndicatorPadding, int progressIndicatorPadding,
}) { }) {
printStatus(message); printStatus(message);
...@@ -366,7 +374,7 @@ class Status { ...@@ -366,7 +374,7 @@ class Status {
onFinish(); onFinish();
} }
/// Call to cancel the spinner after failure or cancelation. /// Call to cancel the spinner after failure or cancellation.
void cancel() { void cancel() {
assert(_isStarted); assert(_isStarted);
_isStarted = false; _isStarted = false;
...@@ -375,18 +383,25 @@ class Status { ...@@ -375,18 +383,25 @@ class Status {
} }
} }
/// An [AnsiSpinner] is a simple animation that does nothing but implement an /// An [AnsiSpinner] is a simple animation that does nothing but implement a
/// ASCII spinner. When stopped or canceled, the animation erases itself. /// ASCII/Unicode spinner. When stopped or canceled, the animation erases
/// itself.
class AnsiSpinner extends Status { class AnsiSpinner extends Status {
AnsiSpinner({VoidCallback onFinish}) : super(onFinish: onFinish); AnsiSpinner({VoidCallback onFinish}) : super(onFinish: onFinish);
int ticks = 0; int ticks = 0;
Timer timer; Timer timer;
static final List<String> _progress = <String>[r'-', r'\', r'|', r'/']; // Windows console font has a limited set of Unicode characters.
List<String> get _animation => platform.isWindows
? <String>[r'-', r'\', r'|', r'/']
: <String>['🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔'];
String get _backspace => '\b' * _animation[0].length;
String get _clear => ' ' * _animation[0].length;
void _callback(Timer timer) { void _callback(Timer timer) {
stdout.write('\b${_progress[ticks++ % _progress.length]}'); stdout.write('$_backspace${_animation[ticks++ % _animation.length]}');
} }
@override @override
...@@ -402,7 +417,7 @@ class AnsiSpinner extends Status { ...@@ -402,7 +417,7 @@ class AnsiSpinner extends Status {
void stop() { void stop() {
assert(timer.isActive); assert(timer.isActive);
timer.cancel(); timer.cancel();
stdout.write('\b \b'); stdout.write('$_backspace$_clear$_backspace');
super.stop(); super.stop();
} }
...@@ -410,7 +425,7 @@ class AnsiSpinner extends Status { ...@@ -410,7 +425,7 @@ class AnsiSpinner extends Status {
void cancel() { void cancel() {
assert(timer.isActive); assert(timer.isActive);
timer.cancel(); timer.cancel();
stdout.write('\b \b'); stdout.write('$_backspace$_clear$_backspace');
super.cancel(); super.cancel();
} }
} }
...@@ -423,23 +438,29 @@ class AnsiStatus extends AnsiSpinner { ...@@ -423,23 +438,29 @@ class AnsiStatus extends AnsiSpinner {
AnsiStatus({ AnsiStatus({
String message, String message,
bool expectSlowOperation, bool expectSlowOperation,
bool multilineOutput,
int padding, int padding,
VoidCallback onFinish, VoidCallback onFinish,
}) : message = message ?? '', }) : message = message ?? '',
padding = padding ?? 0, padding = padding ?? 0,
expectSlowOperation = expectSlowOperation ?? false, expectSlowOperation = expectSlowOperation ?? false,
multilineOutput = multilineOutput ?? false,
super(onFinish: onFinish); super(onFinish: onFinish);
final String message; final String message;
final bool expectSlowOperation; final bool expectSlowOperation;
final bool multilineOutput;
final int padding; final int padding;
Stopwatch stopwatch; Stopwatch stopwatch;
static const String _margin = ' ';
@override @override
void start() { void start() {
assert(stopwatch == null || !stopwatch.isRunning);
stopwatch = Stopwatch()..start(); stopwatch = Stopwatch()..start();
stdout.write('${message.padRight(padding)} '); stdout.write('${message.padRight(padding)}$_margin');
super.start(); super.start();
} }
...@@ -456,17 +477,23 @@ class AnsiStatus extends AnsiSpinner { ...@@ -456,17 +477,23 @@ class AnsiStatus extends AnsiSpinner {
stdout.write('\n'); stdout.write('\n');
} }
/// Backs up 4 characters and prints a (minimum) 5 character padded time. If /// Print summary information when a task is done.
/// [expectSlowOperation] is true, the time is in seconds; otherwise, ///
/// milliseconds. Only backs up 4 characters because [super.cancel] backs /// If [multilineOutput] is false, backs up 4 characters and prints a
/// up one. /// (minimum) 5 character padded time. If [expectSlowOperation] is true, the
/// time is in seconds; otherwise, milliseconds. Only backs up 4 characters
/// because [super.cancel] backs up one.
/// ///
/// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms' /// If [multilineOutput] is true, then it prints the message again on a new
/// line before writing the elapsed time, and doesn't back up at all.
void writeSummaryInformation() { void writeSummaryInformation() {
final String prefix = multilineOutput
? '\n${'$message Done'.padRight(padding - 4)}$_margin'
: '\b\b\b\b';
if (expectSlowOperation) { if (expectSlowOperation) {
stdout.write('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}'); stdout.write('$prefix${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
} else { } else {
stdout.write('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}'); stdout.write('$prefix${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
} }
} }
} }
......
...@@ -772,6 +772,7 @@ class NotifyingLogger extends Logger { ...@@ -772,6 +772,7 @@ class NotifyingLogger extends Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation = false, bool expectSlowOperation = false,
bool multilineOutput,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
printStatus(message); printStatus(message);
...@@ -928,6 +929,7 @@ class _AppRunLogger extends Logger { ...@@ -928,6 +929,7 @@ class _AppRunLogger extends Logger {
String message, { String message, {
String progressId, String progressId,
bool expectSlowOperation = false, bool expectSlowOperation = false,
bool multilineOutput,
int progressIndicatorPadding = 52, int progressIndicatorPadding = 52,
}) { }) {
final int id = _nextProgressId++; final int id = _nextProgressId++;
......
...@@ -32,30 +32,35 @@ class _StdoutHandler { ...@@ -32,30 +32,35 @@ class _StdoutHandler {
reset(); reset();
} }
bool compilerMessageReceived = false;
final CompilerMessageConsumer consumer; final CompilerMessageConsumer consumer;
String boundaryKey; String boundaryKey;
Completer<CompilerOutput> compilerOutput; Completer<CompilerOutput> compilerOutput;
bool _suppressCompilerMessages; bool _suppressCompilerMessages;
void handler(String string) { void handler(String message) {
const String kResultPrefix = 'result '; const String kResultPrefix = 'result ';
if (boundaryKey == null) { if (boundaryKey == null) {
if (string.startsWith(kResultPrefix)) if (message.startsWith(kResultPrefix))
boundaryKey = string.substring(kResultPrefix.length); boundaryKey = message.substring(kResultPrefix.length);
} else if (string.startsWith(boundaryKey)) { } else if (message.startsWith(boundaryKey)) {
if (string.length <= boundaryKey.length) { if (message.length <= boundaryKey.length) {
compilerOutput.complete(null); compilerOutput.complete(null);
return; return;
} }
final int spaceDelimiter = string.lastIndexOf(' '); final int spaceDelimiter = message.lastIndexOf(' ');
compilerOutput.complete( compilerOutput.complete(
CompilerOutput( CompilerOutput(
string.substring(boundaryKey.length + 1, spaceDelimiter), message.substring(boundaryKey.length + 1, spaceDelimiter),
int.parse(string.substring(spaceDelimiter + 1).trim()))); int.parse(message.substring(spaceDelimiter + 1).trim())));
} }
else if (!_suppressCompilerMessages) { else if (!_suppressCompilerMessages) {
consumer('compiler message: $string'); if (compilerMessageReceived == false) {
consumer('\nCompiler message:');
compilerMessageReceived = true;
}
consumer(message);
} }
} }
...@@ -63,6 +68,7 @@ class _StdoutHandler { ...@@ -63,6 +68,7 @@ class _StdoutHandler {
// with its own boundary key and new completer. // with its own boundary key and new completer.
void reset({bool suppressCompilerMessages = false}) { void reset({bool suppressCompilerMessages = false}) {
boundaryKey = null; boundaryKey = null;
compilerMessageReceived = false;
compilerOutput = Completer<CompilerOutput>(); compilerOutput = Completer<CompilerOutput>();
_suppressCompilerMessages = suppressCompilerMessages; _suppressCompilerMessages = suppressCompilerMessages;
} }
...@@ -174,7 +180,7 @@ class KernelCompiler { ...@@ -174,7 +180,7 @@ class KernelCompiler {
server.stderr server.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.listen((String s) { printError('compiler message: $s'); }); .listen((String message) { printError(message); });
server.stdout server.stdout
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
...@@ -242,7 +248,7 @@ class _CompileExpressionRequest extends _CompilationRequest { ...@@ -242,7 +248,7 @@ class _CompileExpressionRequest extends _CompilationRequest {
/// restarts the Flutter app. /// restarts the Flutter app.
class ResidentCompiler { class ResidentCompiler {
ResidentCompiler(this._sdkRoot, {bool trackWidgetCreation = false, ResidentCompiler(this._sdkRoot, {bool trackWidgetCreation = false,
String packagesPath, List<String> fileSystemRoots, String fileSystemScheme , String packagesPath, List<String> fileSystemRoots, String fileSystemScheme,
CompilerMessageConsumer compilerMessageConsumer = printError, CompilerMessageConsumer compilerMessageConsumer = printError,
String initializeFromDill}) String initializeFromDill})
: assert(_sdkRoot != null), : assert(_sdkRoot != null),
...@@ -381,7 +387,7 @@ class ResidentCompiler { ...@@ -381,7 +387,7 @@ class ResidentCompiler {
_server.stderr _server.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen((String s) { printError('compiler message: $s'); }); .listen((String message) { printError(message); });
_server.stdin.writeln('compile $scriptFilename'); _server.stdin.writeln('compile $scriptFilename');
......
...@@ -413,7 +413,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -413,7 +413,7 @@ Future<XcodeBuildResult> buildXcodeProject({
Status initialBuildStatus; Status initialBuildStatus;
Directory tempDir; Directory tempDir;
if (logger.supportsColor) { if (logger.hasTerminal) {
tempDir = fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.'); tempDir = fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
final File scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout'); final File scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
os.makePipe(scriptOutputPipeFile.path); os.makePipe(scriptOutputPipeFile.path);
......
...@@ -217,7 +217,7 @@ class _Compiler { ...@@ -217,7 +217,7 @@ class _Compiler {
if (suppressOutput) if (suppressOutput)
return; return;
if (message.startsWith('compiler message: Error: Could not resolve the package \'test\'')) { if (message.startsWith('Error: Could not resolve the package \'test\'')) {
printTrace(message); printTrace(message);
printError( printError(
'\n\nFailed to load test harness. Are you missing a dependency on flutter_test?\n', '\n\nFailed to load test harness. Are you missing a dependency on flutter_test?\n',
......
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import '../src/common.dart'; import '../src/common.dart';
...@@ -60,6 +61,7 @@ void main() { ...@@ -60,6 +61,7 @@ void main() {
AnsiStatus ansiStatus; AnsiStatus ansiStatus;
SummaryStatus summaryStatus; SummaryStatus summaryStatus;
int called; int called;
const List<String> testPlatforms = <String>['linux', 'macos', 'windows', 'fuchsia'];
final RegExp secondDigits = RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)'); final RegExp secondDigits = RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)');
setUp(() { setUp(() {
...@@ -91,22 +93,105 @@ void main() { ...@@ -91,22 +93,105 @@ void main() {
}); });
} }
testUsingContext('AnsiSpinner works', () async { for (String testOs in testPlatforms) {
ansiSpinner.start(); testUsingContext('AnsiSpinner works for $testOs', () async {
await doWhileAsync(() => ansiSpinner.ticks < 10); ansiSpinner.start();
List<String> lines = outputStdout(); await doWhileAsync(() => ansiSpinner.ticks < 10);
expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/')); List<String> lines = outputStdout();
expect(lines[0].endsWith('\n'), isFalse); expect(lines[0], startsWith(platform.isWindows
expect(lines.length, equals(1)); ? ' \b-\b\\\b|\b/\b-\b\\\b|\b/'
ansiSpinner.stop(); : ' \b\b🌕\b\b🌖\b\b🌗\b\b🌘\b\b🌑\b\b🌒\b\b🌓\b\b🌔\b\b🌕\b\b🌖'));
lines = outputStdout(); expect(lines[0].endsWith('\n'), isFalse);
expect(lines[0], endsWith('\b \b')); expect(lines.length, equals(1));
expect(lines.length, equals(1)); ansiSpinner.stop();
lines = outputStdout();
expect(lines[0], endsWith(platform.isWindows ? '\b \b' : '\b\b \b\b'));
expect(lines.length, equals(1));
// Verify that stopping or canceling multiple times throws.
expect(() {
ansiSpinner.stop();
}, throwsA(isInstanceOf<AssertionError>()));
expect(() {
ansiSpinner.cancel();
}, throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Platform: () => FakePlatform(operatingSystem: testOs),
});
// Verify that stopping or canceling multiple times throws. testUsingContext('Stdout startProgress handle null inputs on colored terminal for $testOs', () async {
expect(() { ansiSpinner.stop(); }, throwsA(isInstanceOf<AssertionError>())); context[Logger].startProgress(null, progressId: null,
expect(() { ansiSpinner.cancel(); }, throwsA(isInstanceOf<AssertionError>())); expectSlowOperation: null,
}, overrides: <Type, Generator>{Stdio: () => mockStdio}); progressIndicatorPadding: null,
);
final List<String> lines = outputStdout();
expect(outputStderr().length, equals(1));
expect(outputStderr().first, isEmpty);
expect(lines[0], matches(platform.isWindows ? r'[ ]{64} [\b]-' : r'[ ]{64} [\b][\b]🌕'));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Platform: () => FakePlatform(operatingSystem: testOs),
Logger: () => StdoutLogger()..supportsColor = true,
});
testUsingContext('AnsiStatus works when cancelled for $testOs', () async {
ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10);
List<String> lines = outputStdout();
expect(lines[0], startsWith(platform.isWindows
? 'Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/'
: 'Hello world \b\b🌕\b\b🌖\b\b🌗\b\b🌘\b\b🌑\b\b🌒\b\b🌓\b\b🌔\b\b🌕\b\b🌖'));
expect(lines.length, equals(1));
expect(lines[0].endsWith('\n'), isFalse);
// Verify a cancel does _not_ print the time and prints a newline.
ansiStatus.cancel();
lines = outputStdout();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isEmpty);
expect(lines[0], endsWith(platform.isWindows ? '\b \b' : '\b\b \b\b'));
expect(called, equals(1));
expect(lines.length, equals(2));
expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws.
expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Platform: () => FakePlatform(operatingSystem: testOs),
});
testUsingContext('AnsiStatus works when stopped for $testOs', () async {
ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10);
List<String> lines = outputStdout();
expect(lines[0], startsWith(platform.isWindows
? 'Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/'
: 'Hello world \b\b🌕\b\b🌖\b\b🌗\b\b🌘\b\b🌑\b\b🌒\b\b🌓\b\b🌔\b\b🌕\b\b🌖'));
expect(lines.length, equals(1));
// Verify a stop prints the time.
ansiStatus.stop();
lines = outputStdout();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isNotNull);
expect(matches, hasLength(1));
final Match match = matches.first;
expect(lines[0], endsWith(match.group(0)));
expect(called, equals(1));
expect(lines.length, equals(2));
expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws.
expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Platform: () => FakePlatform(operatingSystem: testOs),
});
}
testUsingContext('Error logs are red', () async { testUsingContext('Error logs are red', () async {
context[Logger].printError('Pants on fire!'); context[Logger].printError('Pants on fire!');
...@@ -144,20 +229,6 @@ void main() { ...@@ -144,20 +229,6 @@ void main() {
Logger: () => StdoutLogger()..supportsColor = true, Logger: () => StdoutLogger()..supportsColor = true,
}); });
testUsingContext('Stdout startProgress handle null inputs on colored terminal', () async {
context[Logger].startProgress(null, progressId: null,
expectSlowOperation: null,
progressIndicatorPadding: null,
);
final List<String> lines = outputStdout();
expect(outputStderr().length, equals(1));
expect(outputStderr().first, isEmpty);
expect(lines[0], equals(' \b-'));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Logger: () => StdoutLogger()..supportsColor = true,
});
testUsingContext('Stdout printStatus handle null inputs on regular terminal', () async { testUsingContext('Stdout printStatus handle null inputs on regular terminal', () async {
context[Logger].printStatus(null, emphasis: null, context[Logger].printStatus(null, emphasis: null,
color: null, color: null,
...@@ -180,59 +251,12 @@ void main() { ...@@ -180,59 +251,12 @@ void main() {
final List<String> lines = outputStdout(); final List<String> lines = outputStdout();
expect(outputStderr().length, equals(1)); expect(outputStderr().length, equals(1));
expect(outputStderr().first, isEmpty); expect(outputStderr().first, isEmpty);
expect(lines[0], equals(' ')); expect(lines[0], matches('[ ]{64}'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Stdio: () => mockStdio, Stdio: () => mockStdio,
Logger: () => StdoutLogger()..supportsColor = false, Logger: () => StdoutLogger()..supportsColor = false,
}); });
testUsingContext('AnsiStatus works when cancelled', () async {
ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10);
List<String> lines = outputStdout();
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
expect(lines.length, equals(1));
expect(lines[0].endsWith('\n'), isFalse);
// Verify a cancel does _not_ print the time and prints a newline.
ansiStatus.cancel();
lines = outputStdout();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isEmpty);
expect(lines[0], endsWith('\b \b'));
expect(called, equals(1));
expect(lines.length, equals(2));
expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws.
expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
testUsingContext('AnsiStatus works when stopped', () async {
ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10);
List<String> lines = outputStdout();
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
expect(lines.length, equals(1));
// Verify a stop prints the time.
ansiStatus.stop();
lines = outputStdout();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isNotNull);
expect(matches, hasLength(1));
final Match match = matches.first;
expect(lines[0], endsWith(match.group(0)));
expect(called, equals(1));
expect(lines.length, equals(2));
expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws.
expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
testUsingContext('SummaryStatus works when cancelled', () async { testUsingContext('SummaryStatus works when cancelled', () async {
summaryStatus.start(); summaryStatus.start();
List<String> lines = outputStdout(); List<String> lines = outputStdout();
......
...@@ -116,7 +116,11 @@ Future<Null> _testFile(String testName, String workingDirectory, String testDire ...@@ -116,7 +116,11 @@ Future<Null> _testFile(String testName, String workingDirectory, String testDire
int outputLineNumber = 0; int outputLineNumber = 0;
bool haveSeenStdErrMarker = false; bool haveSeenStdErrMarker = false;
while (expectationLineNumber < expectations.length) { while (expectationLineNumber < expectations.length) {
expect(output, hasLength(greaterThan(outputLineNumber))); expect(
output,
hasLength(greaterThan(outputLineNumber)),
reason: 'Failure in $testName to compare to $fullTestExpectation',
);
final String expectationLine = expectations[expectationLineNumber]; final String expectationLine = expectations[expectationLineNumber];
final String outputLine = output[outputLineNumber]; final String outputLine = output[outputLineNumber];
if (expectationLine == '<<skip until matching line>>') { if (expectationLine == '<<skip until matching line>>') {
......
...@@ -50,10 +50,11 @@ void main() { ...@@ -50,10 +50,11 @@ void main() {
mainPath: '/path/to/main.dart' mainPath: '/path/to/main.dart'
); );
expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n')); expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
expect(output.outputFilename, equals('/path/to/main.dart.dill')); expect(output.outputFilename, equals('/path/to/main.dart.dill'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('single dart failed compilation', () async { testUsingContext('single dart failed compilation', () async {
...@@ -70,10 +71,11 @@ void main() { ...@@ -70,10 +71,11 @@ void main() {
mainPath: '/path/to/main.dart' mainPath: '/path/to/main.dart'
); );
expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n')); expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
expect(output, equals(null)); expect(output, equals(null));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('single dart abnormal compiler termination', () async { testUsingContext('single dart abnormal compiler termination', () async {
...@@ -92,10 +94,11 @@ void main() { ...@@ -92,10 +94,11 @@ void main() {
mainPath: '/path/to/main.dart' mainPath: '/path/to/main.dart'
); );
expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n')); expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
expect(output, equals(null)); expect(output, equals(null));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
}); });
...@@ -146,10 +149,11 @@ void main() { ...@@ -146,10 +149,11 @@ void main() {
); );
expect(mockFrontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); expect(mockFrontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n');
verifyNoMoreInteractions(mockFrontendServerStdIn); verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n')); expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
expect(output.outputFilename, equals('/path/to/main.dart.dill')); expect(output.outputFilename, equals('/path/to/main.dart.dill'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('single dart compile abnormally terminates', () async { testUsingContext('single dart compile abnormally terminates', () async {
...@@ -163,6 +167,7 @@ void main() { ...@@ -163,6 +167,7 @@ void main() {
expect(output, equals(null)); expect(output, equals(null));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('compile and recompile', () async { testUsingContext('compile and recompile', () async {
...@@ -181,11 +186,12 @@ void main() { ...@@ -181,11 +186,12 @@ void main() {
verifyNoMoreInteractions(mockFrontendServerStdIn); verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(logger.errorText, equals( expect(logger.errorText, equals(
'compiler message: line0\ncompiler message: line1\n' '\nCompiler message:\nline0\nline1\n'
'compiler message: line1\ncompiler message: line2\n' '\nCompiler message:\nline1\nline2\n'
)); ));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('compile and recompile twice', () async { testUsingContext('compile and recompile twice', () async {
...@@ -208,12 +214,13 @@ void main() { ...@@ -208,12 +214,13 @@ void main() {
verifyNoMoreInteractions(mockFrontendServerStdIn); verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(logger.errorText, equals( expect(logger.errorText, equals(
'compiler message: line0\ncompiler message: line1\n' '\nCompiler message:\nline0\nline1\n'
'compiler message: line1\ncompiler message: line2\n' '\nCompiler message:\nline1\nline2\n'
'compiler message: line2\ncompiler message: line3\n' '\nCompiler message:\nline2\nline3\n'
)); ));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
}); });
...@@ -283,7 +290,7 @@ void main() { ...@@ -283,7 +290,7 @@ void main() {
'compile /path/to/main.dart\n'); 'compile /path/to/main.dart\n');
verifyNoMoreInteractions(mockFrontendServerStdIn); verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(logger.errorText, expect(logger.errorText,
equals('compiler message: line1\ncompiler message: line2\n')); equals('\nCompiler message:\nline1\nline2\n'));
expect(output.outputFilename, equals('/path/to/main.dart.dill')); expect(output.outputFilename, equals('/path/to/main.dart.dill'));
compileExpressionResponseCompleter.complete( compileExpressionResponseCompleter.complete(
...@@ -302,6 +309,7 @@ void main() { ...@@ -302,6 +309,7 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
testUsingContext('compile expressions without awaiting', () async { testUsingContext('compile expressions without awaiting', () async {
...@@ -325,7 +333,7 @@ void main() { ...@@ -325,7 +333,7 @@ void main() {
'/path/to/main.dart', null /* invalidatedFiles */ '/path/to/main.dart', null /* invalidatedFiles */
).then((CompilerOutput outputCompile) { ).then((CompilerOutput outputCompile) {
expect(logger.errorText, expect(logger.errorText,
equals('compiler message: line1\ncompiler message: line2\n')); equals('\nCompiler message:\nline1\nline2\n'));
expect(outputCompile.outputFilename, equals('/path/to/main.dart.dill')); expect(outputCompile.outputFilename, equals('/path/to/main.dart.dill'));
compileExpressionResponseCompleter1.complete(Future<List<int>>.value(utf8.encode( compileExpressionResponseCompleter1.complete(Future<List<int>>.value(utf8.encode(
...@@ -363,6 +371,7 @@ void main() { ...@@ -363,6 +371,7 @@ void main() {
expect(await lastExpressionCompleted.future, isTrue); expect(await lastExpressionCompleted.future, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Logger: () => BufferLogger()..supportsColor = false,
}); });
}); });
} }
......
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