From 63575dd86310747c723cc5f2ca9d00e815296ed8 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Tue, 18 Aug 2015 16:22:32 -0500 Subject: [PATCH] #29 Travis CI --- .travis.yml | 12 ++++++++ README.md | 2 ++ lib/src/tasks/coverage/api.dart | 40 ++++++++++++++++++------- lib/src/tasks/coverage/cli.dart | 2 +- lib/src/tasks/test/api.dart | 9 ++++-- lib/src/tasks/test/cli.dart | 12 +++++++- lib/src/tasks/test/config.dart | 2 ++ lib/src/util.dart | 26 ++++++++++++++++ lib/util.dart | 1 + test/integration/copy_license_test.dart | 6 ++-- test/integration/coverage_test.dart | 2 +- test/integration/format_test.dart | 6 ++-- tool/dev.dart | 6 ++-- 13 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..0bd62317 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: dart +dart: + - stable +with_content_shell: true +before_install: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +script: + - pub run dart_dev format --check + - pub run dart_dev analyze + - pub run dart_dev test --integration + - pub run dart_dev coverage --integration --no-html \ No newline at end of file diff --git a/README.md b/README.md index e6354d56..569c389d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Dart Dev Tools +[![Build Status](https://travis-ci.org/Workiva/dart_dev.svg?branch=master)](https://travis-ci.org/Workiva/dart_dev) > Centralized tooling for Dart projects. Consistent interface across projects. Easily configurable. @@ -211,6 +212,7 @@ All configuration options for the `test` task are found on the `config.test` obj Name | Type | Default | Description ------------------ | -------------- | ----------- | ----------- +`concurrency` | `int` | `4` | Number of concurrent test suites run. `integrationTests` | `List` | `[]` | Integration test locations. Items in this list can be directories and/or files. `platforms` | `List` | `[]` | Platforms on which to run the tests (handled by the Dart test runner). See https://github.com/dart-lang/test#platform-selector-syntax for a full list of supported platforms. `unitTests` | `List` | `['test/']` | Unit test locations. Items in this list can be directories and/or files. diff --git a/lib/src/tasks/coverage/api.dart b/lib/src/tasks/coverage/api.dart index 5443c14f..889c293c 100644 --- a/lib/src/tasks/coverage/api.dart +++ b/lib/src/tasks/coverage/api.dart @@ -38,16 +38,18 @@ class CoverageResult extends TaskResult { {Directory report}) : super.fail(), this.report = report, - reportIndex = - report != null ? new File('${report.path}/index.html') : null; + reportIndex = report != null + ? new File(path.join(report.path, 'index.html')) + : null; CoverageResult.success( Iterable this.tests, File this.collection, File this.lcov, {Directory report}) : super.success(), this.report = report, - reportIndex = - report != null ? new File('${report.path}/index.html') : null; + reportIndex = report != null + ? new File(path.join(report.path, 'index.html')) + : null; } class CoverageTask extends Task { @@ -204,7 +206,7 @@ class CoverageTask extends Task { List collections = []; for (int i = 0; i < _files.length; i++) { File collection = new File(path.join( - '${_outputDirectory.path}/collection', '${_files[i].path}.json')); + _outputDirectory.path, 'collection', '${_files[i].path}.json')); int observatoryPort; // Run the test and obtain the observatory port for coverage collection. @@ -243,8 +245,7 @@ class CoverageTask extends Task { } Future _format(List reportOn) async { - _lcov = new File('${_outputDirectory.path}/coverage.lcov'); - _lcov.createSync(); + _lcov = new File(path.join(_outputDirectory.path, 'coverage.lcov')); String executable = 'pub'; List args = [ @@ -268,6 +269,16 @@ class CoverageTask extends Task { process.stdout.listen((l) => _coverageOutput.add(' $l')); process.stderr.listen((l) => _coverageErrorOutput.add(' $l')); await process.done; + + if (lcov.existsSync()) { + _coverageOutput.add(''); + _coverageOutput.add('Coverage formatted to LCOV: ${lcov.path}'); + } else { + String error = + 'Coverage formatting failed. Could not generate ${lcov.path}'; + _coverageErrorOutput.add(error); + throw new Exception(error); + } } Future _generateReport() async { @@ -303,7 +314,7 @@ class CoverageTask extends Task { collections[i].deleteSync(); } - File coverage = new File('${_outputDirectory.path}/coverage.json'); + File coverage = new File(path.join(_outputDirectory.path, 'coverage.json')); if (coverage.existsSync()) { coverage.deleteSync(); } @@ -392,10 +403,19 @@ class CoverageTask extends Task { _coverageOutput.add('$executable ${args.join(' ')}\n'); TaskProcess process = _lastTestProcess = new TaskProcess('content_shell', args); - process.stdout.listen((l) => _coverageOutput.add(' $l')); + // 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; - // Note: content-shell dumps render tree to stderr. + 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)) { diff --git a/lib/src/tasks/coverage/cli.dart b/lib/src/tasks/coverage/cli.dart index c9a22f4e..975e4b47 100644 --- a/lib/src/tasks/coverage/cli.dart +++ b/lib/src/tasks/coverage/cli.dart @@ -85,7 +85,7 @@ class CoverageCli extends TaskCli { reporter.logGroup('Collecting coverage', outputStream: task.output, errorStream: task.errorOutput); CoverageResult result = await task.done; - if (result.successful && open) { + if (result.successful && html && open) { Process.run('open', [result.reportIndex.path]); } return result.successful diff --git a/lib/src/tasks/test/api.dart b/lib/src/tasks/test/api.dart index 2687b87b..97adcdce 100644 --- a/lib/src/tasks/test/api.dart +++ b/lib/src/tasks/test/api.dart @@ -21,14 +21,19 @@ import 'package:dart_dev/util.dart' show TaskProcess; import 'package:dart_dev/src/tasks/task.dart'; TestTask test( - {List platforms: const [], List tests: const []}) { + {int concurrency, + List platforms: const [], + List tests: const []}) { var executable = 'pub'; var args = ['run', 'test']; + if (concurrency != null) { + args.add('--concurrency=$concurrency'); + } platforms.forEach((p) { args.addAll(['-p', p]); }); - args.addAll(tests); args.addAll(['--reporter=expanded']); + args.addAll(tests); TaskProcess process = new TaskProcess(executable, args); Completer outputProcessed = new Completer(); diff --git a/lib/src/tasks/test/cli.dart b/lib/src/tasks/test/cli.dart index 37bc45cc..79e15957 100644 --- a/lib/src/tasks/test/cli.dart +++ b/lib/src/tasks/test/cli.dart @@ -32,6 +32,10 @@ class TestCli extends TaskCli { ..addFlag('integration', defaultsTo: defaultIntegration, help: 'Includes the integration test suite.') + ..addOption('concurrency', + abbr: 'j', + defaultsTo: '$defaultConcurrency', + help: 'The number of concurrent test suites run.') ..addOption('platform', abbr: 'p', allowMultiple: true, @@ -46,6 +50,11 @@ class TestCli extends TaskCli { bool unit = parsedArgs['unit']; bool integration = parsedArgs['integration']; + var concurrency = + TaskCli.valueOf('concurrency', parsedArgs, config.test.concurrency); + if (concurrency is String) { + concurrency = int.parse(concurrency); + } List platforms = TaskCli.valueOf('platform', parsedArgs, config.test.platforms); @@ -72,7 +81,8 @@ class TestCli extends TaskCli { } } - TestTask task = test(platforms: platforms, tests: tests); + TestTask task = + test(tests: tests, concurrency: concurrency, platforms: platforms); reporter.logGroup(task.testCommand, outputStream: task.testOutput); await task.done; return task.successful diff --git a/lib/src/tasks/test/config.dart b/lib/src/tasks/test/config.dart index d94838f2..9044d98f 100644 --- a/lib/src/tasks/test/config.dart +++ b/lib/src/tasks/test/config.dart @@ -16,6 +16,7 @@ library dart_dev.src.tasks.test.config; import 'package:dart_dev/src/tasks/config.dart'; +const int defaultConcurrency = 4; const bool defaultIntegration = false; const List defaultIntegrationTests = const []; const bool defaultUnit = true; @@ -23,6 +24,7 @@ const List defaultUnitTests = const ['test/']; const List defaultPlatforms = const []; class TestConfig extends TaskConfig { + int concurrency = defaultConcurrency; List integrationTests = defaultIntegrationTests; List platforms = defaultPlatforms; List unitTests = defaultUnitTests; diff --git a/lib/src/util.dart b/lib/src/util.dart index 6073d3e9..3e2ea45e 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -17,8 +17,34 @@ library dart_dev.src.util; import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as path; import 'package:yaml/yaml.dart'; +void copyDirectory(Directory source, Directory dest) { + if (!dest.existsSync()) { + dest.createSync(recursive: true); + } + + source.listSync(recursive: true).forEach((entity) { + if (FileSystemEntity.isDirectorySync(entity.path)) { + Directory orig = entity; + String p = path.relative(orig.path, from: source.path); + p = path.join(dest.path, p); + Directory copy = new Directory(p); + if (!copy.existsSync()) { + copy.createSync(recursive: true); + } + } else if (FileSystemEntity.isFileSync(entity.path)) { + File orig = entity; + String p = path.relative(orig.path, from: source.path); + p = path.join(dest.path, p); + File copy = new File(p); + copy.createSync(recursive: true); + copy.writeAsBytesSync(orig.readAsBytesSync()); + } + }); +} + /// Returns an open port by creating a temporary Socket. /// Borrowed from coverage package https://github.com/dart-lang/coverage/blob/master/lib/src/util.dart#L49-L66 Future getOpenPort() async { diff --git a/lib/util.dart b/lib/util.dart index ad4f6f9c..0924251f 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -18,6 +18,7 @@ export 'package:dart_dev/src/reporter.dart' show Reporter, reporter; export 'package:dart_dev/src/task_process.dart' show TaskProcess; export 'package:dart_dev/src/util.dart' show + copyDirectory, getOpenPort, hasImmediateDependency, parseArgsFromCommand, diff --git a/test/integration/copy_license_test.dart b/test/integration/copy_license_test.dart index 84b81707..20a97c99 100644 --- a/test/integration/copy_license_test.dart +++ b/test/integration/copy_license_test.dart @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +@TestOn('vm') library dart_dev.test.integration.copy_license_test; import 'dart:async'; import 'dart:io'; -import 'package:dart_dev/util.dart' show TaskProcess; +import 'package:dart_dev/util.dart' show TaskProcess, copyDirectory; import 'package:test/test.dart'; const String projectWithLicenses = 'test/fixtures/copy_license/has_licenses'; @@ -28,8 +29,7 @@ const String projectWithoutLicenses = 'test/fixtures/copy_license/no_licenses'; Future createTemporaryProject(String source) async { String tempProject = '${source}_temp'; Directory temp = new Directory(tempProject); - temp.createSync(); - await Process.run('cp', ['-R', '$source/', tempProject]); + copyDirectory(new Directory(source), temp); return tempProject; } diff --git a/test/integration/coverage_test.dart b/test/integration/coverage_test.dart index cdd6ede6..d5cc658c 100644 --- a/test/integration/coverage_test.dart +++ b/test/integration/coverage_test.dart @@ -33,7 +33,7 @@ Future runCoverage(String projectPath) async { oldCoverage.deleteSync(recursive: true); } - List args = ['run', 'dart_dev', 'coverage', '--no-open']; + List args = ['run', 'dart_dev', 'coverage', '--no-html']; TaskProcess process = new TaskProcess('pub', args, workingDirectory: projectPath); diff --git a/test/integration/format_test.dart b/test/integration/format_test.dart index 4cdb34a6..861f28bc 100644 --- a/test/integration/format_test.dart +++ b/test/integration/format_test.dart @@ -18,7 +18,7 @@ library dart_dev.test.integration.format_test; import 'dart:async'; import 'dart:io'; -import 'package:dart_dev/util.dart' show TaskProcess; +import 'package:dart_dev/util.dart' show TaskProcess, copyDirectory; import 'package:test/test.dart'; const String projectWithChangesNeeded = 'test/fixtures/format/changes_needed'; @@ -71,9 +71,7 @@ void main() { // testing purposes (necessary since formatter will make changes). String testProject = '${projectWithChangesNeeded}_temp'; Directory temp = new Directory(testProject); - temp.createSync(); - await Process.run( - 'cp', ['-R', '$projectWithChangesNeeded/', testProject]); + copyDirectory(new Directory(projectWithChangesNeeded), temp); File dirtyFile = new File('$testProject/lib/main.dart'); File cleanFile = new File('$projectWithNoChangesNeeded/lib/main.dart'); diff --git a/tool/dev.dart b/tool/dev.dart index a6c6b48e..57f425e7 100644 --- a/tool/dev.dart +++ b/tool/dev.dart @@ -23,8 +23,10 @@ main(args) async { config.coverage.reportOn = ['bin/', 'lib/']; config.format.directories = directories; config.test - ..unitTests = [] - ..integrationTests = ['test/integration/']; + ..concurrency = 1 + ..platforms = ['vm'] + ..integrationTests = ['test/integration/'] + ..unitTests = []; await dev(args); }