| // Copyright 2014 The Flutter 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 'dart:async'; |
| import 'dart:convert'; |
| |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/dart/sdk.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| |
| // This test depends on some files in ///dev/automated_tests/flutter_test/* |
| |
| Future<void> _testExclusionLock; |
| |
| void main() { |
| final String automatedTestsDirectory = globals.fs.path.join('..', '..', 'dev', 'automated_tests'); |
| final String flutterTestDirectory = globals.fs.path.join(automatedTestsDirectory, 'flutter_test'); |
| |
| testUsingContext('flutter test should not have extraneous error messages', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('trivial_widget', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero); |
| }); |
| |
| testUsingContext('flutter test should report nice errors for exceptions thrown within testWidgets()', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory); |
| }); |
| |
| testUsingContext('flutter test should report a nice error when a guarded function was called without await', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory); |
| }); |
| |
| testUsingContext('flutter test should report a nice error when an async function was called without await', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory); |
| }); |
| |
| testUsingContext('flutter test should report a nice error when a Ticker is left running', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory); |
| }); |
| |
| testUsingContext('flutter test should report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async { |
| final String missingDependencyTests = globals.fs.path.join('..', '..', 'dev', 'missing_dependency_tests'); |
| Cache.flutterRoot = '../..'; |
| return _testFile('trivial', missingDependencyTests, missingDependencyTests); |
| }); |
| |
| testUsingContext('flutter test should report which user created widget caused the error', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('print_user_created_ancestor', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--track-widget-creation']); |
| }); |
| |
| testUsingContext('flutter test should report which user created widget caused the error - no flag', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('print_user_created_ancestor_no_flag', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--no-track-widget-creation']); |
| }); |
| |
| testUsingContext('flutter test should report correct created widget caused the error', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('print_correct_local_widget', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--track-widget-creation']); |
| }); |
| |
| testUsingContext('flutter test should can load assets within its own package', () async { |
| Cache.flutterRoot = '../..'; |
| return _testFile('package_assets', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero); |
| }); |
| |
| testUsingContext('flutter test should run a test when its name matches a regexp', () async { |
| Cache.flutterRoot = '../..'; |
| final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--name', 'inc.*de']); |
| if (!(result.stdout as String).contains('+1: All tests passed')) { |
| fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); |
| } |
| expect(result.exitCode, 0); |
| }); |
| |
| testUsingContext('flutter test should run a test when its name contains a string', () async { |
| Cache.flutterRoot = '../..'; |
| final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--plain-name', 'include']); |
| if (!(result.stdout as String).contains('+1: All tests passed')) { |
| fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); |
| } |
| expect(result.exitCode, 0); |
| }); |
| |
| testUsingContext('flutter test should test runs to completion', () async { |
| Cache.flutterRoot = '../..'; |
| final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory, |
| extraArguments: const <String>['--verbose']); |
| final String stdout = result.stdout as String; |
| if ((!stdout.contains('+1: All tests passed')) || |
| (!stdout.contains('test 0: starting shell process')) || |
| (!stdout.contains('test 0: deleting temporary directory')) || |
| (!stdout.contains('test 0: finished')) || |
| (!stdout.contains('test package returned with exit code 0'))) { |
| fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); |
| } |
| if ((result.stderr as String).isNotEmpty) { |
| fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); |
| } |
| expect(result.exitCode, 0); |
| }); |
| |
| testUsingContext('flutter test should run all tests inside of a directory with no trailing slash', () async { |
| Cache.flutterRoot = '../..'; |
| final ProcessResult result = await _runFlutterTest(null, automatedTestsDirectory, flutterTestDirectory + '/child_directory', |
| extraArguments: const <String>['--verbose']); |
| final String stdout = result.stdout as String; |
| if ((!stdout.contains('+2: All tests passed')) || |
| (!stdout.contains('test 0: starting shell process')) || |
| (!stdout.contains('test 0: deleting temporary directory')) || |
| (!stdout.contains('test 0: finished')) || |
| (!stdout.contains('test package returned with exit code 0'))) { |
| fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); |
| } |
| if ((result.stderr as String).isNotEmpty) { |
| fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); |
| } |
| expect(result.exitCode, 0); |
| }); |
| } |
| |
| Future<void> _testFile( |
| String testName, |
| String workingDirectory, |
| String testDirectory, { |
| Matcher exitCode, |
| List<String> extraArguments = const <String>[], |
| }) async { |
| exitCode ??= isNonZero; |
| final String fullTestExpectation = globals.fs.path.join(testDirectory, '${testName}_expectation.txt'); |
| final File expectationFile = globals.fs.file(fullTestExpectation); |
| if (!expectationFile.existsSync()) { |
| fail('missing expectation file: $expectationFile'); |
| } |
| |
| while (_testExclusionLock != null) { |
| await _testExclusionLock; |
| } |
| |
| final ProcessResult exec = await _runFlutterTest( |
| testName, |
| workingDirectory, |
| testDirectory, |
| extraArguments: extraArguments, |
| ); |
| |
| expect(exec.exitCode, exitCode); |
| final List<String> output = (exec.stdout as String).split('\n'); |
| if (output.first == 'Waiting for another flutter command to release the startup lock...') { |
| output.removeAt(0); |
| } |
| if (output.first.startsWith('Running "flutter pub get" in')) { |
| output.removeAt(0); |
| } |
| output.add('<<stderr>>'); |
| output.addAll((exec.stderr as String).split('\n')); |
| final List<String> expectations = globals.fs.file(fullTestExpectation).readAsLinesSync(); |
| bool allowSkip = false; |
| int expectationLineNumber = 0; |
| int outputLineNumber = 0; |
| bool haveSeenStdErrMarker = false; |
| while (expectationLineNumber < expectations.length) { |
| expect( |
| output, |
| hasLength(greaterThan(outputLineNumber)), |
| reason: 'Failure in $testName to compare to $fullTestExpectation', |
| ); |
| final String expectationLine = expectations[expectationLineNumber]; |
| String outputLine = output[outputLineNumber]; |
| if (expectationLine == '<<skip until matching line>>') { |
| allowSkip = true; |
| expectationLineNumber += 1; |
| continue; |
| } |
| if (allowSkip) { |
| if (!RegExp(expectationLine).hasMatch(outputLine)) { |
| outputLineNumber += 1; |
| continue; |
| } |
| allowSkip = false; |
| } |
| if (expectationLine == '<<stderr>>') { |
| expect(haveSeenStdErrMarker, isFalse); |
| haveSeenStdErrMarker = true; |
| } |
| if (!RegExp(expectationLine).hasMatch(outputLine) && outputLineNumber + 1 < output.length) { |
| // Check if the RegExp can match the next two lines in the output so |
| // that it is possible to write expectations that still hold even if a |
| // line is wrapped slightly differently due to for example a file name |
| // being longer on one platform than another. |
| final String mergedLines = '$outputLine\n${output[outputLineNumber+1]}'; |
| if (RegExp(expectationLine).hasMatch(mergedLines)) { |
| outputLineNumber += 1; |
| outputLine = mergedLines; |
| } |
| } |
| |
| expect(outputLine, matches(expectationLine), reason: 'Full output:\n- - - -----8<----- - - -\n${output.join("\n")}\n- - - -----8<----- - - -'); |
| expectationLineNumber += 1; |
| outputLineNumber += 1; |
| } |
| expect(allowSkip, isFalse); |
| if (!haveSeenStdErrMarker) { |
| expect(exec.stderr, ''); |
| } |
| } |
| |
| Future<ProcessResult> _runFlutterTest( |
| String testName, |
| String workingDirectory, |
| String testDirectory, { |
| List<String> extraArguments = const <String>[], |
| }) async { |
| |
| String testPath; |
| if (testName == null) { |
| // Test everything in the directory. |
| testPath = testDirectory; |
| final Directory directoryToTest = globals.fs.directory(testPath); |
| if (!directoryToTest.existsSync()) { |
| fail('missing test directory: $directoryToTest'); |
| } |
| } else { |
| // Test just a specific test file. |
| testPath = globals.fs.path.join(testDirectory, '${testName}_test.dart'); |
| final File testFile = globals.fs.file(testPath); |
| if (!testFile.existsSync()) { |
| fail('missing test file: $testFile'); |
| } |
| } |
| |
| final List<String> args = <String>[ |
| ...dartVmFlags, |
| globals.fs.path.absolute(globals.fs.path.join('bin', 'flutter_tools.dart')), |
| 'test', |
| '--no-color', |
| '--no-version-check', |
| ...extraArguments, |
| testPath, |
| ]; |
| |
| while (_testExclusionLock != null) { |
| await _testExclusionLock; |
| } |
| |
| final Completer<void> testExclusionCompleter = Completer<void>(); |
| _testExclusionLock = testExclusionCompleter.future; |
| try { |
| return await Process.run( |
| globals.fs.path.join(dartSdkPath, 'bin', 'dart'), |
| args, |
| workingDirectory: workingDirectory, |
| stdoutEncoding: utf8, |
| stderrEncoding: utf8, |
| ); |
| } finally { |
| _testExclusionLock = null; |
| testExclusionCompleter.complete(); |
| } |
| } |