diff --git a/dev/automated_tests/flutter_test/filtering_test.dart b/dev/automated_tests/flutter_test/filtering_test.dart new file mode 100644 index 0000000000..1cf3277f46 --- /dev/null +++ b/dev/automated_tests/flutter_test/filtering_test.dart @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:test/test.dart'; + +void main() { + test('included', () { + expect(2 + 2, 4); + }); + test('excluded', () { + throw "this test should have been filtered out"; + }); +} diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 2f2e58e219..91de53fd90 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -22,6 +22,18 @@ import '../test/watcher.dart'; class TestCommand extends FlutterCommand { TestCommand({ bool verboseHelp: false }) { usesPubOption(); + argParser.addOption('name', + help: 'A regular expression matching substrings of the names of tests to run.', + valueHelp: 'regexp', + allowMultiple: true, + splitCommas: false, + ); + argParser.addOption('plain-name', + help: 'A plain-text substring of the names of tests to run.', + valueHelp: 'substring', + allowMultiple: true, + splitCommas: false, + ); argParser.addFlag('start-paused', defaultsTo: false, negatable: false, @@ -141,6 +153,8 @@ class TestCommand extends FlutterCommand { } commandValidator(); + final List names = argResults['name']; + final List plainNames = argResults['plain-name']; Iterable files = argResults.rest.map((String testPath) => fs.path.absolute(testPath)).toList(); @@ -189,6 +203,8 @@ class TestCommand extends FlutterCommand { final int result = await runTests(files, workDir: workDir, + names: names, + plainNames: plainNames, watcher: watcher, enableObservatory: collector != null || startPaused, startPaused: startPaused, diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index c376313ec9..0d127d80ce 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -21,6 +21,8 @@ import 'watcher.dart'; Future runTests( List testFiles, { Directory workDir, + List names: const [], + List plainNames: const [], bool enableObservatory: false, bool startPaused: false, bool ipv6: false, @@ -41,6 +43,14 @@ Future runTests( testArgs.addAll(['-r', 'json']); } + for (String name in names) { + testArgs..add("--name")..add(name); + } + + for (String plainName in plainNames) { + testArgs..add("--plain-name")..add(plainName); + } + testArgs.add('--'); testArgs.addAll(testFiles); diff --git a/packages/flutter_tools/test/commands/test_test.dart b/packages/flutter_tools/test/commands/test_test.dart index bd3c043f64..450e126727 100644 --- a/packages/flutter_tools/test/commands/test_test.dart +++ b/packages/flutter_tools/test/commands/test_test.dart @@ -18,63 +18,63 @@ import '../src/context.dart'; Future _testExclusionLock; void main() { - group('test', () { + group('flutter test should', () { final String automatedTestsDirectory = fs.path.join('..', '..', 'dev', 'automated_tests'); final String flutterTestDirectory = fs.path.join(automatedTestsDirectory, 'flutter_test'); - testUsingContext('Exception handling in test harness', () async { + testUsingContext('report nice errors for exceptions thrown within testWidgets()', () async { Cache.flutterRoot = '../..'; return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory); }); - testUsingContext('TestAsyncUtils guarded function test', () async { + testUsingContext('report a nice error when a guarded function was called without await', () async { Cache.flutterRoot = '../..'; return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory); }); - testUsingContext('TestAsyncUtils unguarded function test', () async { + testUsingContext('report a nice error when an async function was called without await', () async { Cache.flutterRoot = '../..'; return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory); }); - testUsingContext('Missing flutter_test dependency', () async { + testUsingContext('report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async { final String missingDependencyTests = fs.path.join('..', '..', 'dev', 'missing_dependency_tests'); Cache.flutterRoot = '../..'; return _testFile('trivial', missingDependencyTests, missingDependencyTests); }); + + testUsingContext('run a test when its name matches a regexp', () async { + Cache.flutterRoot = '../..'; + final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, + extraArgs: const ["--name", "inc.*de"]); + if (!result.stdout.contains("+1: All tests passed")) + fail("unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n"); + expect(result.exitCode, 0); + }); + + testUsingContext('run a test when its name contains a string', () async { + Cache.flutterRoot = '../..'; + final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, + extraArgs: const ["--plain-name", "include"]); + if (!result.stdout.contains("+1: All tests passed")) + fail("unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n"); + expect(result.exitCode, 0); + }); + }, skip: io.Platform.isWindows); // TODO(goderbauer): enable when sky_shell is available } Future _testFile(String testName, String workingDirectory, String testDirectory) async { - final String fullTestName = fs.path.join(testDirectory, '${testName}_test.dart'); - final File testFile = fs.file(fullTestName); - expect(testFile.existsSync(), true); final String fullTestExpectation = fs.path.join(testDirectory, '${testName}_expectation.txt'); final File expectationFile = fs.file(fullTestExpectation); - expect(expectationFile.existsSync(), true); + if (!expectationFile.existsSync()) + fail("missing expectation file: $expectationFile"); while (_testExclusionLock != null) await _testExclusionLock; - ProcessResult exec; - final Completer testExclusionCompleter = new Completer(); - _testExclusionLock = testExclusionCompleter.future; - try { - exec = await Process.run( - fs.path.join(dartSdkPath, 'bin', 'dart'), - [ - fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')), - 'test', - '--no-color', - fullTestName, - ], - workingDirectory: workingDirectory, - ); - } finally { - _testExclusionLock = null; - testExclusionCompleter.complete(); - } + final ProcessResult exec = await _runFlutterTest(testName, workingDirectory, testDirectory); expect(exec.exitCode, isNonZero); final List output = exec.stdout.split('\n'); @@ -115,3 +115,34 @@ Future _testFile(String testName, String workingDirectory, String testDire if (!haveSeenStdErrMarker) expect(exec.stderr, ''); } + +Future _runFlutterTest(String testName, String workingDirectory, String testDirectory, + {List extraArgs = const []}) async { + + final String testFilePath = fs.path.join(testDirectory, '${testName}_test.dart'); + final File testFile = fs.file(testFilePath); + if (!testFile.existsSync()) + fail("missing test file: $testFile"); + + final List args = [ + fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')), + 'test', + '--no-color' + ]..addAll(extraArgs)..add(testFilePath); + + while (_testExclusionLock != null) + await _testExclusionLock; + + final Completer testExclusionCompleter = new Completer(); + _testExclusionLock = testExclusionCompleter.future; + try { + return await Process.run( + fs.path.join(dartSdkPath, 'bin', 'dart'), + args, + workingDirectory: workingDirectory, + ); + } finally { + _testExclusionLock = null; + testExclusionCompleter.complete(); + } +}