Created
February 11, 2019 00:28
-
-
Save dnfield/fc2badb1aa4161313b112905e8e99831 to your computer and use it in GitHub Desktop.
Wrapper for flutter test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:io'; | |
import 'package:meta/meta.dart'; | |
final Stopwatch _stopwatch = Stopwatch(); | |
final bool useColor = stdout.supportsAnsiEscapes; | |
/// The terminal escape for green text, or the empty string if this is Windows | |
/// or not outputting to a terminal. | |
String get _green => useColor ? '\u001b[32m' : ''; | |
/// The terminal escape for red text, or the empty string if this is Windows | |
/// or not outputting to a terminal. | |
String get _red => useColor ? '\u001b[31m' : ''; | |
/// The terminal escape for yellow text, or the empty string if this is | |
/// Windows or not outputting to a terminal. | |
String get _yellow => useColor ? '\u001b[33m' : ''; | |
/// The terminal escape for gray text, or the empty string if this is | |
/// Windows or not outputting to a terminal. | |
String get _gray => useColor ? '\u001b[1;30m' : ''; | |
/// The terminal escape for bold text, or the empty string if this is | |
/// Windows or not outputting to a terminal. | |
String get _bold => useColor ? '\u001b[1m' : ''; | |
/// The terminal escape for removing test coloring, or the empty string if | |
/// this is Windows or not outputting to a terminal. | |
String get _noColor => useColor ? '\u001b[0m' : ''; | |
/// The termianl escape for clearing the line, or a carriage return if | |
/// this is Windows or not outputting to a termianl. | |
String get _clearLine => useColor ? '\x1b[2K\r' : '\r'; | |
Future<void> main(List<String> args) async { | |
final Process testProcess = await Process.start( | |
'flutter', | |
['test', '--machine']..addAll(args), | |
workingDirectory: '/Users/dnfield/src/flutter/packages/flutter', | |
); | |
_stopwatch.start(); | |
print('$_bold${_gray}test process started, pid: ${testProcess.pid}$_noColor'); | |
testProcess.stdout | |
.transform<String>(utf8.decoder) | |
.transform<String>(const LineSplitter()) | |
.listen(_processTestJson); | |
testProcess.stderr.listen((List<int> data) => stderr.add(data)); | |
} | |
final Map<int, TestResult> tests = <int, TestResult>{}; | |
int started = 0; | |
int failures = 0; | |
int skips = 0; | |
int successes = 0; | |
void _processTestJson(String raw) { | |
if (!raw.startsWith('{')) { | |
print(raw); | |
return; | |
} | |
final Map<String, dynamic> decoded = json.decode(raw); | |
final TestResult originalResult = tests[decoded['testID']]; | |
switch (decoded['type']) { | |
case 'done': | |
stdout.write(_clearLine); | |
stdout.write('$_bold${_stopwatch.elapsed}$_noColor '); | |
stdout.writeln('$_green+$successes $_yellow~$skips $_red-$failures:$_bold$_gray Done.$_noColor'); | |
_done(); | |
break; | |
case 'testStart': | |
final Map<String, dynamic> testData = decoded['test']; | |
if (testData['url'] == null) { | |
// load a test file. | |
started += 1; | |
stdout.write(_clearLine); | |
stdout.write('$_bold${_stopwatch.elapsed}$_noColor '); | |
stdout.write('$_green+$successes $_yellow~$skips $_red-$failures: $_gray${testData['name']}$_noColor'); | |
break; | |
} | |
tests[testData['id']] = TestResult( | |
id: testData['id'], | |
name: testData['name'], | |
line: testData['root_line'] ?? testData['line'], | |
column: testData['root_column'] ?? testData['column'], | |
path: testData['root_url'] ?? testData['url'], | |
startTime: decoded['time'], | |
); | |
break; | |
case 'testDone': | |
if (originalResult == null) { | |
break; | |
} | |
originalResult.endTime = decoded['time']; | |
if (decoded['skipped'] == true) { | |
skips += 1; | |
originalResult.status = TestStatus.skipped; | |
} else { | |
if (decoded['result'] == 'success') { | |
tests.remove(originalResult.id); | |
// originalResult.status = TestStatus.succeeded; | |
successes += 1; | |
} else { | |
originalResult.status = TestStatus.failed; | |
failures += 1; | |
} | |
} | |
break; | |
case 'error': | |
if (originalResult != null) { | |
originalResult.errorMessage = decoded['error']; | |
originalResult.stackTrace = decoded['stackTrace']; | |
} | |
break; | |
case 'print': | |
if (originalResult != null) { | |
originalResult.messages.add(decoded['message']); | |
} | |
break; | |
case 'group': | |
case 'allSuites': | |
case 'start': | |
case 'suite': | |
default: | |
break; | |
} | |
} | |
void _done() { | |
List<String> skipped = <String>[]; | |
List<String> failed = <String>[]; | |
for (TestResult result in tests.values) { | |
switch (result.status) { | |
case TestStatus.started: | |
failed.add('${_red}Unexpectedly failed to complete a test!'); | |
failed.add(result.toString() + _noColor); | |
break; | |
case TestStatus.skipped: | |
skipped.add('${_yellow}Skipped ${result.name} (${result.pathLineColumn}).$_noColor'); | |
break; | |
case TestStatus.failed: | |
failed.addAll([ | |
'$_bold${_red}Failed ${result.name} (${result.pathLineColumn}):', | |
result.errorMessage, | |
_noColor + _red, | |
result.stackTrace, | |
]); | |
failed.addAll(result.messages); | |
failed.add(_noColor); | |
break; | |
case TestStatus.succeeded: | |
break; | |
} | |
} | |
skipped.forEach(print); | |
failed.forEach(print); | |
if (failed.length == 0) { | |
print('${_green}Completed, $successes test(s) passing ($skips skipped).$_noColor'); | |
} else { | |
print('${_gray}$failures test(s) failed, setting non-zero exit code.$_noColor'); | |
exit(-1); | |
} | |
} | |
enum TestStatus { | |
started, | |
succeeded, | |
failed, | |
skipped, | |
} | |
class TestResult { | |
TestResult({ | |
@required this.id, | |
@required this.name, | |
@required this.line, | |
@required this.column, | |
@required this.path, | |
@required this.startTime, | |
this.status = TestStatus.started, | |
}) : assert(id != null), | |
assert(name != null), | |
assert(line != null), | |
assert(column != null), | |
assert(path != null), | |
assert(startTime != null), | |
assert(status != null), | |
messages = <String>[]; | |
TestStatus status; | |
final int id; | |
final String name; | |
final int line; | |
final int column; | |
final String path; | |
String get pathLineColumn => '$path:$line:$column'; | |
final int startTime; | |
final List<String> messages; | |
String errorMessage; | |
String stackTrace; | |
int endTime; | |
int get totalTime => (endTime ?? _stopwatch.elapsedMilliseconds) - startTime; | |
@override | |
String toString() => '{$runtimeType: {$id, $name, ${totalTime}ms, $pathLineColumn}}'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment