test_test.dart 14.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8 9 10
// TODO(gspencergoog): Remove this tag once this test's state leaks/test
// dependencies have been fixed.
// https://github.com/flutter/flutter/issues/85160
// Fails with "flutter test --test-randomize-ordering-seed=1000"
@Tags(<String>['no-shuffle'])

11
import 'dart:async';
12
import 'dart:convert';
13

14
import 'package:flutter_tools/src/base/file_system.dart';
15
import 'package:flutter_tools/src/base/io.dart';
16

17 18
import '../src/common.dart';
import 'test_utils.dart';
19 20 21

// This test depends on some files in ///dev/automated_tests/flutter_test/*

22 23 24
final String automatedTestsDirectory = fileSystem.path.join('..', '..', 'dev', 'automated_tests');
final String missingDependencyDirectory = fileSystem.path.join('..', '..', 'dev', 'missing_dependency_tests');
final String flutterTestDirectory = fileSystem.path.join(automatedTestsDirectory, 'flutter_test');
25
final String integrationTestDirectory = fileSystem.path.join(automatedTestsDirectory, 'integration_test');
26
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
27

28 29 30 31
// Running Integration Tests in the Flutter Tester will still exercise the same
// flows specific to Integration Tests.
final List<String> integrationTestExtraArgs = <String>['-d', 'flutter-tester'];

32
void main() {
33 34 35 36 37
  setUpAll(() async {
    await processManager.run(
      <String>[
        flutterBin,
        'pub',
38
        'get',
39 40 41 42 43 44 45
      ],
      workingDirectory: flutterTestDirectory
    );
    await processManager.run(
      <String>[
        flutterBin,
        'pub',
46
        'get',
47 48 49 50
      ],
      workingDirectory: missingDependencyDirectory
    );
  });
51

52
  testWithoutContext('flutter test should not have extraneous error messages', () async {
53 54
    return _testFile('trivial_widget', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
  });
55

56 57 58 59
  testWithoutContext('integration test should not have extraneous error messages', () async {
    return _testFile('trivial_widget', automatedTestsDirectory, integrationTestDirectory, exitCode: isZero, extraArguments: integrationTestExtraArgs);
  });

60 61 62 63
  testWithoutContext('flutter test set the working directory correctly', () async {
    return _testFile('working_directory', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
  });

64
  testWithoutContext('flutter test should report nice errors for exceptions thrown within testWidgets()', () async {
65 66
    return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory);
  });
67

68 69 70 71
  testWithoutContext('integration test should report nice errors for exceptions thrown within testWidgets()', () async {
    return _testFile('exception_handling', automatedTestsDirectory, integrationTestDirectory, extraArguments: integrationTestExtraArgs);
  });

72
  testWithoutContext('flutter test should report a nice error when a guarded function was called without await', () async {
73 74
    return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory);
  });
75

76
  testWithoutContext('flutter test should report a nice error when an async function was called without await', () async {
77 78
    return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory);
  });
79

80
  testWithoutContext('flutter test should report a nice error when a Ticker is left running', () async {
81 82
    return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory);
  });
83

84 85
  testWithoutContext('flutter test should report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async {
    final String missingDependencyTests = fileSystem.path.join('..', '..', 'dev', 'missing_dependency_tests');
86 87
    return _testFile('trivial', missingDependencyTests, missingDependencyTests);
  });
88

89
  testWithoutContext('flutter test should report which user-created widget caused the error', () async {
90
    return _testFile('print_user_created_ancestor', automatedTestsDirectory, flutterTestDirectory,
91
        extraArguments: const <String>['--track-widget-creation']);
92
  });
93

94
  testWithoutContext('flutter test should report which user-created widget caused the error - no flag', () async {
95 96 97
    return _testFile('print_user_created_ancestor_no_flag', automatedTestsDirectory, flutterTestDirectory,
       extraArguments: const <String>['--no-track-widget-creation']);
  });
98

99
  testWithoutContext('flutter test should report the correct user-created widget that caused the error', () async {
100 101 102
    return _testFile('print_correct_local_widget', automatedTestsDirectory, flutterTestDirectory,
      extraArguments: const <String>['--track-widget-creation']);
  });
103

104
  testWithoutContext('flutter test should can load assets within its own package', () async {
105 106
    return _testFile('package_assets', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
  });
107

108 109 110 111 112
  testWithoutContext('flutter test should support dart defines', () async {
    return _testFile('dart_defines', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero,
      extraArguments: <String>['--dart-define=flutter.test.foo=bar']);
  });

113
  testWithoutContext('flutter test should run a test when its name matches a regexp', () async {
114 115
    final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
      extraArguments: const <String>['--name', 'inc.*de']);
116
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
117 118
    expect(result.exitCode, 0);
  });
119

120
  testWithoutContext('flutter test should run a test when its name contains a string', () async {
121 122
    final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
      extraArguments: const <String>['--plain-name', 'include']);
123
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
124 125
    expect(result.exitCode, 0);
  });
126

127
  testWithoutContext('flutter test should run a test with a given tag', () async {
128 129
    final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory,
        extraArguments: const <String>['--tags', 'include-tag']);
130
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
131 132 133
    expect(result.exitCode, 0);
  });

134
  testWithoutContext('flutter test should not run a test with excluded tag', () async {
135 136
    final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory,
        extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
137
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
138 139 140
    expect(result.exitCode, 0);
  });

141
  testWithoutContext('flutter test should run all tests when tags are unspecified', () async {
142
    final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory);
143
    expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.')));
144 145 146
    expect(result.exitCode, 1);
  });

147
  testWithoutContext('flutter test should run a widgetTest with a given tag', () async {
148 149
    final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
        extraArguments: const <String>['--tags', 'include-tag']);
150
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
151 152 153
    expect(result.exitCode, 0);
  });

154
  testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async {
155 156
    final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
        extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
157
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
158 159 160
    expect(result.exitCode, 0);
  });

161
  testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async {
162
    final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory);
163
    expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.')));
164 165 166
    expect(result.exitCode, 1);
  });

167
  testWithoutContext('flutter test should test runs to completion', () async {
168 169
    final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
      extraArguments: const <String>['--verbose']);
170 171 172 173 174 175
    final String stdout = (result.stdout as String).replaceAll('\r', '\n');
    expect(stdout, contains(RegExp(r'\+\d+: All tests passed\!')));
    expect(stdout, contains('test 0: Starting flutter_tester process with command'));
    expect(stdout, contains('test 0: deleting temporary directory'));
    expect(stdout, contains('test 0: finished'));
    expect(stdout, contains('test package returned with exit code 0'));
176 177 178 179 180 181
    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);
  });

182
  testWithoutContext('flutter test should run all tests inside of a directory with no trailing slash', () async {
183
    final ProcessResult result = await _runFlutterTest(null, automatedTestsDirectory, '$flutterTestDirectory/child_directory',
184
      extraArguments: const <String>['--verbose']);
185 186 187 188 189 190
    final String stdout = (result.stdout as String).replaceAll('\r', '\n');
    expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed\!')));
    expect(stdout, contains('test 0: Starting flutter_tester process with command'));
    expect(stdout, contains('test 0: deleting temporary directory'));
    expect(stdout, contains('test 0: finished'));
    expect(stdout, contains('test package returned with exit code 0'));
191 192 193 194
    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);
195
  });
196 197 198 199

  testWithoutContext('flutter gold skips tests where the expectations are missing', () async {
    return _testFile('flutter_gold', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
  });
200 201
}

202
Future<void> _testFile(
203 204 205
  String testName,
  String workingDirectory,
  String testDirectory, {
206
  Matcher? exitCode,
207 208
  List<String> extraArguments = const <String>[],
}) async {
209
  exitCode ??= isNonZero;
210 211
  final String fullTestExpectation = fileSystem.path.join(testDirectory, '${testName}_expectation.txt');
  final File expectationFile = fileSystem.file(fullTestExpectation);
212
  if (!expectationFile.existsSync()) {
213
    fail('missing expectation file: $expectationFile');
214
  }
215

216 217 218 219 220 221
  final ProcessResult exec = await _runFlutterTest(
    testName,
    workingDirectory,
    testDirectory,
    extraArguments: extraArguments,
  );
222

223
  expect(exec.exitCode, exitCode);
224
  final List<String> output = (exec.stdout as String).split('\n');
225
  if (output.first.startsWith('Waiting for another flutter command to release the startup lock...')) {
226
    output.removeAt(0);
227 228
  }
  if (output.first.startsWith('Running "flutter pub get" in')) {
229
    output.removeAt(0);
230
  }
231 232 233
  // Whether cached artifacts need to be downloaded is dependent on what
  // previous tests have run. Disregard these messages.
  output.removeWhere(RegExp(r'Downloading .*\.\.\.').hasMatch);
234
  output.add('<<stderr>>');
235
  output.addAll((exec.stderr as String).split('\n'));
236
  final List<String> expectations = fileSystem.file(fullTestExpectation).readAsLinesSync();
237 238 239
  bool allowSkip = false;
  int expectationLineNumber = 0;
  int outputLineNumber = 0;
240
  bool haveSeenStdErrMarker = false;
241
  while (expectationLineNumber < expectations.length) {
242 243 244 245 246
    expect(
      output,
      hasLength(greaterThan(outputLineNumber)),
      reason: 'Failure in $testName to compare to $fullTestExpectation',
    );
247
    final String expectationLine = expectations[expectationLineNumber];
248
    String outputLine = output[outputLineNumber];
249 250 251 252 253 254
    if (expectationLine == '<<skip until matching line>>') {
      allowSkip = true;
      expectationLineNumber += 1;
      continue;
    }
    if (allowSkip) {
255
      if (!RegExp(expectationLine).hasMatch(outputLine)) {
256 257 258 259 260
        outputLineNumber += 1;
        continue;
      }
      allowSkip = false;
    }
261 262 263 264
    if (expectationLine == '<<stderr>>') {
      expect(haveSeenStdErrMarker, isFalse);
      haveSeenStdErrMarker = true;
    }
265 266 267 268 269 270 271 272 273 274 275 276
    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;
      }
    }

277
    expect(outputLine, matches(expectationLine), reason: 'Full output:\n- - - -----8<----- - - -\n${output.join("\n")}\n- - - -----8<----- - - -');
278 279 280 281
    expectationLineNumber += 1;
    outputLineNumber += 1;
  }
  expect(allowSkip, isFalse);
282
  if (!haveSeenStdErrMarker) {
283
    expect(exec.stderr, '');
284
  }
285
}
286

287
Future<ProcessResult> _runFlutterTest(
288
  String? testName,
289 290
  String workingDirectory,
  String testDirectory, {
291
  List<String> extraArguments = const <String>[],
292 293
}) async {

294 295 296 297
  String testPath;
  if (testName == null) {
    // Test everything in the directory.
    testPath = testDirectory;
298
    final Directory directoryToTest = fileSystem.directory(testPath);
299
    if (!directoryToTest.existsSync()) {
300
      fail('missing test directory: $directoryToTest');
301
    }
302 303
  } else {
    // Test just a specific test file.
304 305
    testPath = fileSystem.path.join(testDirectory, '${testName}_test.dart');
    final File testFile = fileSystem.file(testPath);
306
    if (!testFile.existsSync()) {
307
      fail('missing test file: $testFile');
308
    }
309
  }
310

311 312 313
  final List<String> args = <String>[
    'test',
    '--no-color',
314
    '--no-version-check',
315
    '--no-pub',
316
    ...extraArguments,
317
    testPath,
318
  ];
319

320 321 322 323 324 325 326
  return Process.run(
    flutterBin, // Uses the precompiled flutter tool for faster tests,
    args,
    workingDirectory: workingDirectory,
    stdoutEncoding: utf8,
    stderrEncoding: utf8,
  );
327
}