Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pubspec.lock

# generated assets in test fixtures
/test/fixtures/coverage/browser/coverage/
/test/fixtures/coverage/browser_needs_pub_serve/coverage/
/test/fixtures/coverage/non_test_file/coverage/
/test/fixtures/coverage/vm/coverage/
/test/fixtures/docs/docs/doc/api/
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,12 @@ configuration from the `config.test` object.
<td><code>['lib/']</code></td>
<td>List of paths to include in the generated coverage report (LCOV and HTML).</td>
</tr>
<tr>
<td><code>pubServe</code></td>
<td><code>bool</code></td>
<td><code>false</code></td>
<td>Whether or not to serve browser tests using a Pub server.<br>If <code>true</code>, make sure to follow the <code>test</code> package's <a href="https://github.com/dart-lang/test#testing-with-barback">setup instructions</a> and include the <code>test/pub_serve</code> transformer.</td>
</tr>
</tbody>
</table>

Expand Down Expand Up @@ -413,6 +419,12 @@ object.
<td><code>['test/']</code></td>
<td>Unit test locations. Items in this list can be directories and/or files.</td>
</tr>
<tr>
<td><code>pubServe</code></td>
<td><code>bool</code></td>
<td><code>false</code></td>
<td>Whether or not to serve browser tests using a Pub server.<br>If <code>true</code>, make sure to follow the <code>test</code> package's <a href="https://github.com/dart-lang/test#testing-with-barback">setup instructions</a> and include the <code>test/pub_serve</code> transformer.</td>
</tr>
</tbody>
</table>
* Individual test files can be executed by appending their path to the end of the command.
Expand Down
31 changes: 19 additions & 12 deletions lib/src/reporter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,31 @@ class Reporter {
_log(stdout, message, shout: shout);
}

static String _indent(String lines) {
return ' ' + lines.replaceAll('\n', '\n ');
}

void logGroup(String title,
{String output,
String error,
Stream<String> outputStream,
Stream<String> errorStream}) {
log(colorBlue('\n::: $title'));
if (output != null) {
log('${output.split('\n').join('\n ')}');
return;
}
var formattedTitle = colorBlue('\n::: $title');

if (outputStream != null) {
outputStream.listen((line) {
log(' $line');
if (output != null) {
log(formattedTitle);
log(_indent(output));
} else if (error != null) {
warning(formattedTitle);
warning(_indent(error));
} else {
log(formattedTitle);

outputStream?.listen((line) {
log(_indent(line));
});
}
if (errorStream != null) {
errorStream.listen((line) {
warning(' $line');
errorStream?.listen((line) {
warning(_indent(line));
});
}
}
Expand Down
141 changes: 103 additions & 38 deletions lib/src/tasks/coverage/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import 'package:path/path.dart' as path;
import 'package:dart_dev/src/platform_util/api.dart' as platform_util;
import 'package:dart_dev/src/tasks/coverage/config.dart';
import 'package:dart_dev/src/tasks/coverage/exceptions.dart';
import 'package:dart_dev/src/tasks/serve/api.dart';
import 'package:dart_dev/src/tasks/task.dart';
import 'package:dart_dev/src/tasks/test/config.dart';

const String _dartFilePattern = '.dart';
const String _testFilePattern = '_test.dart';
Expand Down Expand Up @@ -85,14 +87,18 @@ class CoverageTask extends Task {
/// [tests] will be searched (recursively) for all files ending in
/// "_test.dart" and all matching files will be run as tests.
///
/// If [pubServe] is true, a Pub server will be automatically started and
/// used to run any browser tests.
///
/// If [html] is true, `genhtml` will be used to generate an HTML report of
/// the collected coverage and the report will be opened.
static CoverageTask start(List<String> tests,
{bool html: defaultHtml,
bool pubServe: defaultPubServe,
String output: defaultOutput,
List<String> reportOn: defaultReportOn}) {
CoverageTask coverage =
new CoverageTask._(tests, reportOn, html: html, output: output);
CoverageTask coverage = new CoverageTask._(tests, reportOn,
pubServe: pubServe, html: html, output: output);
coverage._run();
return coverage;
}
Expand Down Expand Up @@ -120,6 +126,10 @@ class CoverageTask extends Task {
/// Whether or not to generate the HTML report.
bool _html = defaultHtml;

/// Whether to automatically start and use a Pub server when running
/// browser tests.
final bool pubServe;

/// File created to run the test in a browser. Need to store it so it can be
/// cleaned up after the test finishes.
File _lastHtmlFile;
Expand All @@ -135,7 +145,9 @@ class CoverageTask extends Task {
List<String> _reportOn;

CoverageTask._(List<String> tests, List<String> reportOn,
{bool html: defaultHtml, String output: defaultOutput})
{bool html: defaultHtml,
bool this.pubServe: defaultPubServe,
String output: defaultOutput})
: _html = html,
_outputDirectory = new Directory(output),
_reportOn = reportOn {
Expand Down Expand Up @@ -407,45 +419,98 @@ class CoverageTask extends Task {
String _testsPassedPattern = 'All tests passed!';

if (isBrowserTest) {
// Run the test in content-shell.
String executable = 'content_shell';
List args = [htmlFile.path];
_coverageOutput.add('');
_coverageOutput.add('Running test suite ${file.path}');
_coverageOutput.add('$executable ${args.join(' ')}\n');
TaskProcess process =
_lastTestProcess = new TaskProcess('content_shell', args);
PubServeTask pubServeTask;

// Content-shell dumps render tree to stderr, which is where the test
// results will be. The observatory port should be output to stderr as
// well, but it is sometimes malformed. In those cases, the correct
// observatory port is output to stdout. So we listen to both.
int observatoryPort;
process.stdout.listen((line) {
_coverageOutput.add(' $line');
if (line.contains(_observatoryPortPattern)) {
Match m = _observatoryPortPattern.firstMatch(line);
observatoryPort = int.parse(m.group(2));
}
});
await for (String line in process.stderr) {
_coverageOutput.add(' $line');
if (line.contains(_observatoryFailPattern)) {
throw new CoverageTestSuiteException(file.path);
}
if (line.contains(_observatoryPortPattern)) {
Match m = _observatoryPortPattern.firstMatch(line);
observatoryPort = int.parse(m.group(2));
}
if (line.contains(_testsFailedPattern)) {
throw new CoverageTestSuiteException(file.path);
try {
String testPath;
if (pubServe) {
_coverageOutput.add('Starting Pub server...');

// Start `pub serve` on the `test` directory.
pubServeTask = startPubServe(additionalArgs: ['test']);

_coverageOutput.add('::: ${pubServeTask.command}');
String indentLine(String line) => ' $line';

var startupLogFinished = new Completer();
pubServeTask.stdOut
.transform(until(startupLogFinished.future))
.map(indentLine)
.listen(_coverageOutput.add);
pubServeTask.stdErr
.transform(until(startupLogFinished.future))
.map(indentLine)
.listen(_coverageErrorOutput.add);

PubServeInfo serveInfo = await pubServeTask.serveInfos.first;
if (!path.isWithin(serveInfo.directory, htmlFile.path)) {
throw '`pub serve` directory does not contain test file: ${htmlFile.path}';
}

var relativeHtmlPath =
path.relative(htmlFile.path, from: serveInfo.directory);
testPath = 'http://localhost:${serveInfo.port}/$relativeHtmlPath';

startupLogFinished.complete();
pubServeTask.stdOut.map(indentLine).join('\n').then((stdOut) {
if (stdOut.isNotEmpty) {
_coverageOutput
.add('`${pubServeTask.command}` (buffered stdout)');
_coverageOutput.add(stdOut);
}
});
pubServeTask.stdErr.map(indentLine).join('\n').then((stdErr) {
if (stdErr.isNotEmpty) {
_coverageOutput
.add('`${pubServeTask.command}` (buffered stdout)');
_coverageOutput.add(stdErr);
}
});
} else {
testPath = htmlFile.path;
}
if (line.contains(_testsPassedPattern)) {
break;

// Run the test in content-shell.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff for this section is easier to look at if you ignore whitespace changes.

String executable = 'content_shell';
List args = [testPath];
_coverageOutput.add('');
_coverageOutput.add('Running test suite ${file.path}');
_coverageOutput.add('$executable ${args.join(' ')}\n');
TaskProcess process =
_lastTestProcess = new TaskProcess('content_shell', args);

// Content-shell dumps render tree to stderr, which is where the test
// results will be. The observatory port should be output to stderr as
// well, but it is sometimes malformed. In those cases, the correct
// observatory port is output to stdout. So we listen to both.
int observatoryPort;
process.stdout.listen((line) {
_coverageOutput.add(' $line');
if (line.contains(_observatoryPortPattern)) {
Match m = _observatoryPortPattern.firstMatch(line);
observatoryPort = int.parse(m.group(2));
}
});
await for (String line in process.stderr) {
_coverageOutput.add(' $line');
if (line.contains(_observatoryFailPattern)) {
throw new CoverageTestSuiteException(file.path);
}
if (line.contains(_observatoryPortPattern)) {
Match m = _observatoryPortPattern.firstMatch(line);
observatoryPort = int.parse(m.group(2));
}
if (line.contains(_testsFailedPattern)) {
throw new CoverageTestSuiteException(file.path);
}
if (line.contains(_testsPassedPattern)) {
break;
}
}
return observatoryPort;
} finally {
pubServeTask?.stop();
}

return observatoryPort;
} else {
// Find an open port to observe the Dart VM on.
int port = await getOpenPort();
Expand Down
7 changes: 7 additions & 0 deletions lib/src/tasks/coverage/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class CoverageCli extends TaskCli {
negatable: true,
defaultsTo: defaultHtml,
help: 'Generate and open an HTML report.')
..addFlag('pub-serve',
negatable: true,
defaultsTo: defaultPubServe,
help: 'Serves browser tests using a Pub server.')
..addFlag('open',
negatable: true,
defaultsTo: true,
Expand All @@ -61,6 +65,8 @@ class CoverageCli extends TaskCli {
}

bool html = TaskCli.valueOf('html', parsedArgs, config.coverage.html);
bool pubServe =
TaskCli.valueOf('pub-serve', parsedArgs, config.coverage.pubServe);
bool open = TaskCli.valueOf('open', parsedArgs, true);

List<String> tests = [];
Expand All @@ -85,6 +91,7 @@ class CoverageCli extends TaskCli {
try {
CoverageTask task = CoverageTask.start(tests,
html: html,
pubServe: pubServe,
output: config.coverage.output,
reportOn: config.coverage.reportOn);
reporter.logGroup('Collecting coverage',
Expand Down
2 changes: 2 additions & 0 deletions lib/src/tasks/coverage/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
library dart_dev.src.tasks.coverage.config;

import 'package:dart_dev/src/tasks/config.dart';
import 'package:dart_dev/src/tasks/test/config.dart';

const bool defaultHtml = true;
const String defaultOutput = 'coverage/';
const List<String> defaultReportOn = const ['lib/'];

class CoverageConfig extends TaskConfig {
bool html = defaultHtml;
bool pubServe = defaultPubServe;
String output = defaultOutput;
List<String> reportOn = defaultReportOn;
}
Loading