diff --git a/lib/src/tasks/gen_test_runner/api.dart b/lib/src/tasks/gen_test_runner/api.dart index 42d0401b..cb0df4c1 100644 --- a/lib/src/tasks/gen_test_runner/api.dart +++ b/lib/src/tasks/gen_test_runner/api.dart @@ -20,7 +20,8 @@ import 'dart:io'; import 'package:dart_dev/src/tasks/gen_test_runner/config.dart'; import 'package:dart_dev/src/tasks/task.dart'; -Future genTestRunner(TestRunnerConfig currentConfig) async { +Future genTestRunner(TestRunnerConfig currentConfig, + {List filesToInclude}) async { var taskTitle = 'gen-test-runner'; var args = ['-d ${currentConfig.directory}', '-e ${currentConfig.env}']; currentConfig.genHtml ? args.add('--genHtml') : args.add('--no-genHtml'); @@ -28,13 +29,7 @@ Future genTestRunner(TestRunnerConfig currentConfig) async { GenTestRunnerTask task = new GenTestRunnerTask('$taskTitle ${args.join(' ')}'); - var currentDirectory = currentConfig.directory; - if (!currentDirectory.endsWith('/')) { - currentDirectory += '/'; - } - - File generatedRunner = - new File('${currentDirectory}${currentConfig.filename}.dart'); + File generatedRunner = new File(currentConfig.path); List existingLines; if (currentConfig.check) { @@ -46,29 +41,35 @@ Future genTestRunner(TestRunnerConfig currentConfig) async { } List runnerLines = []; - - Directory testDirectory = new Directory(currentDirectory); List testFiles = []; - final allFiles = testDirectory - .listSync(recursive: true, followLinks: false) - .where((entity) => entity is File) - .toList(); - allFiles.sort((left, right) => left.path.compareTo(right.path)); - allFiles - .where( - (entity) => !entity.path.endsWith('${currentConfig.filename}.dart')) - .forEach((entity) { - if (entity.path.endsWith('_test.dart')) { - testFiles.add(entity); - task.testFiles.add(entity.path); - } else if (entity.path.endsWith('.dart')) { - task.excludedFiles.add(entity.path); + + if (filesToInclude != null) { + for (final filePath in filesToInclude) { + testFiles.add(new File(filePath)); } - }); + } else { + Directory testDirectory = new Directory(currentConfig.directory); + final allFiles = testDirectory + .listSync(recursive: true, followLinks: false) + .where((entity) => entity is File) + .toList(); + allFiles.sort((left, right) => left.path.compareTo(right.path)); + allFiles + .where( + (entity) => !entity.path.endsWith('${currentConfig.filename}.dart')) + .forEach((entity) { + if (entity.path.endsWith('_test.dart')) { + testFiles.add(entity); + task.testFiles.add(entity.path); + } else if (entity.path.endsWith('.dart')) { + task.excludedFiles.add(entity.path); + } + }); + } if (currentConfig.genHtml && !currentConfig.check) { - await testHtmlFileGenerator( - currentDirectory, currentConfig.filename, currentConfig.htmlHeaders); + await testHtmlFileGenerator(currentConfig.directory, currentConfig.filename, + currentConfig.htmlHeaders); } if (currentConfig.env == Environment.browser) { @@ -79,14 +80,15 @@ Future genTestRunner(TestRunnerConfig currentConfig) async { runnerLines.add('@TestOn(\'browser || vm\')'); } runnerLines.add( - 'library ${currentDirectory.replaceAll('/', '.')}${currentConfig.filename};'); + 'library ${currentConfig.normalizedDirectory.replaceAll('/', '.')}${currentConfig.filename};'); runnerLines.add(''); runnerLines.add('// Generated by `pub run dart_dev ${task.generateCommand}`'); runnerLines.add(''); testFiles.forEach((File file) { - var testPath = file.path.replaceFirst(currentDirectory, ''); + var testPath = + file.path.replaceFirst(currentConfig.normalizedDirectory, ''); runnerLines.add( 'import \'${'./' + testPath}\' as ${testPath.replaceAll('/', '_').substring(0, testPath.length - 5)};'); }); @@ -109,7 +111,8 @@ Future genTestRunner(TestRunnerConfig currentConfig) async { } testFiles.forEach((File file) { - var testPath = file.path.replaceFirst(currentDirectory, ''); + var testPath = + file.path.replaceFirst(currentConfig.normalizedDirectory, ''); runnerLines.add( ' ${testPath.replaceAll('/', '_').substring(0, testPath.length - 5)}.main();'); }); diff --git a/lib/src/tasks/gen_test_runner/config.dart b/lib/src/tasks/gen_test_runner/config.dart index ece808ab..dc2731e5 100644 --- a/lib/src/tasks/gen_test_runner/config.dart +++ b/lib/src/tasks/gen_test_runner/config.dart @@ -33,11 +33,15 @@ class TestRunnerConfig { List dartHeaders = defaultDartHeaders; List preTestCommands = defaultPreTestCommands; String directory = defaultDirectory; + String get normalizedDirectory => + directory + (!directory.endsWith('/') ? '/' : ''); Environment env = defaultEnv; String filename = defaultFilename; bool genHtml = defaultGenHtml; List htmlHeaders = defaultHtmlHeaders; + String get path => '$normalizedDirectory$filename.dart'; + TestRunnerConfig( {List this.dartHeaders: defaultDartHeaders, List this.preTestCommands: defaultPreTestCommands, diff --git a/lib/src/tasks/test/cli.dart b/lib/src/tasks/test/cli.dart index ffec5257..8f56c1ae 100644 --- a/lib/src/tasks/test/cli.dart +++ b/lib/src/tasks/test/cli.dart @@ -15,8 +15,13 @@ library dart_dev.src.tasks.test.cli; import 'dart:async'; +import 'dart:io'; import 'package:args/args.dart'; +import 'package:dart_dev/dart_dev.dart'; +import 'package:dart_dev/src/tasks/gen_test_runner/api.dart'; +import 'package:dart_dev/src/tasks/gen_test_runner/cli.dart'; +import 'package:dart_dev/src/tasks/gen_test_runner/config.dart'; import 'package:dart_dev/util.dart' show hasImmediateDependency, isPortBound, reporter, TaskProcess; @@ -39,6 +44,8 @@ class TestCli extends TaskCli { ..addFlag('functional', defaultsTo: defaultFunctional, help: 'Includes the functional test suite.') + ..addFlag('delete-conflicting-outputs', + help: 'Deletes conflicting outputs during the build', negatable: false) ..addFlag('disable-serve-std-out', defaultsTo: defaultDisableServeStdOut, help: 'Disables standard output for pub serve task.') @@ -46,6 +53,12 @@ class TestCli extends TaskCli { abbr: 'j', defaultsTo: '$defaultConcurrency', help: 'The number of concurrent test suites run.') + ..addFlag(_hackFastBuilds, + help: + 'Improves iterative build times by re-writing the generated test runners at runtime.\n' + 'Use this flag and specify the test file you want to run.\nThis flag ' + 'is a no-op if no test files are specified.', + defaultsTo: false) ..addFlag('pub-serve', negatable: true, defaultsTo: defaultPubServe, @@ -55,8 +68,6 @@ class TestCli extends TaskCli { 'Implies --concurrency=1 and --timeout=none.\n' 'Currently only supported for browser tests.', negatable: false) - ..addFlag('delete-conflicting-outputs', - help: 'Deletes conflicting outputs during the build', negatable: false) ..addFlag('release', abbr: 'r', negatable: true, @@ -98,8 +109,8 @@ class TestCli extends TaskCli { } final testArgs = []; - List tests = []; - List buildArgs = []; + final tests = []; + final buildArgs = []; if (!color) { testArgs.add('--no-color'); @@ -161,10 +172,44 @@ class TestCli extends TaskCli { 'files/directories'); } + final mapRunnerToContents = {}; // Build the list of tests to run. if (individualTestsSpecified) { // Individual tests explicitly passed in should override the test suites. - tests.addAll(parsedArgs.rest); + if (dartMajorVersion == 2 && parsedArgs[_hackFastBuilds]) { + reporter.warning( + 'WARNING: You\'re using `${_hackFastBuilds}`. This will re-write the generated test runners in your repo.\n' + 'The test task will attempt to restore your generated runners after completion, but you may ' + 'have to re-run `pub run dart_dev gen-test-runner` and `pub run dart_dev format` if your runners have changed.\n\n'); + final mapConfigToTestFiles = /* tests to include in runner */>{}; + // Construct mapping from config to tests which should be ran in that config + final copyOfConfigs = new List.from(config.genTestRunner.configs); + for (final _config in copyOfConfigs) { + for (final testFilePath in parsedArgs.rest) { + if (testFilePath.contains(_config.directory)) { + mapConfigToTestFiles.putIfAbsent(_config, () => new Set.from([testFilePath])); + mapConfigToTestFiles[_config].add(testFilePath); + } + } + } + + for (final _config in mapConfigToTestFiles.keys) { + copyOfConfigs.remove(_config); + final runnerFile = new File(_config.path); + mapRunnerToContents.putIfAbsent(runnerFile, () => runnerFile.readAsStringSync()); + await genTestRunner(_config, + filesToInclude: mapConfigToTestFiles[_config].toList()); + tests.add(_config.path); + } + + // Empty all other unused generated runners + for (final _config in copyOfConfigs) { + await genTestRunner(_config, filesToInclude: []); + } + } else { + tests.addAll(parsedArgs.rest); + } } else { // Unit and/or integration suites should only run if individual tests // were not specified. @@ -285,8 +330,17 @@ A pub serve instance will not be started.'''); await runAll(config.test.after = config.test.afterFunctionalTests); } + if (parsedArgs[_hackFastBuilds]) { + // Regenerate all runners: + mapRunnerToContents.forEach((file, originalContents) { + file.writeAsStringSync(originalContents); + }); + } + return task.successful ? new CliResult.success(task.testSummary) : new CliResult.fail(task.testSummary); } + + static String _hackFastBuilds = 'hack-fast-builds'; }