Files
packages/script/tool/test/drive_examples_command_test.dart
stuartmorgan-g fdc1ec7c1c [tool] Add initial file-based command skipping (#8928)
Adds initial file-based filtering. This does not attempt to be comprehensive, just to get some low-hanging fruit, and to create a blueprint for anyone to follow in the future when adding more filtering. I expect that once this is in place, what will happen is that as we notice cases where PRs are hitting slow or flaky tests that they clearly don't need to, we can incrementally improve the filtering on demand.

Fixes https://github.com/flutter/flutter/issues/136394
2025-04-18 14:19:25 +00:00

1804 lines
57 KiB
Dart

// Copyright 2013 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:convert';
import 'dart:io' as io;
import 'package:args/command_runner.dart';
import 'package:fake_async/fake_async.dart';
import 'package:file/file.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
import 'package:flutter_plugin_tools/src/drive_examples_command.dart';
import 'package:git/git.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:test/test.dart';
import 'mocks.dart';
import 'util.dart';
const String _fakeIOSDevice = '67d5c3d1-8bdf-46ad-8f6b-b00e2a972dda';
const String _fakeAndroidDevice = 'emulator-1234';
void main() {
group('test drive_example_command', () {
late Platform mockPlatform;
late Directory packagesDir;
late CommandRunner<void> runner;
late RecordingProcessRunner processRunner;
late RecordingProcessRunner gitProcessRunner;
setUp(() {
mockPlatform = MockPlatform();
final GitDir gitDir;
(:packagesDir, :processRunner, :gitProcessRunner, :gitDir) =
configureBaseCommandMocks(platform: mockPlatform);
final DriveExamplesCommand command = DriveExamplesCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: gitDir,
);
runner = CommandRunner<void>(
'drive_examples_command', 'Test for drive_example_command');
runner.addCommand(command);
// TODO(dit): Clean this up, https://github.com/flutter/flutter/issues/151869
mockPlatform.environment['CHANNEL'] = 'master';
mockPlatform.environment['FLUTTER_LOGS_DIR'] = '/path/to/logs';
});
void setMockFlutterDevicesOutput({
bool hasIOSDevice = true,
bool hasAndroidDevice = true,
bool includeBanner = false,
}) {
const String updateBanner = '''
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
''';
final List<String> devices = <String>[
if (hasIOSDevice) '{"id": "$_fakeIOSDevice", "targetPlatform": "ios"}',
if (hasAndroidDevice)
'{"id": "$_fakeAndroidDevice", "targetPlatform": "android-x86"}',
];
final String output =
'''${includeBanner ? updateBanner : ''}[${devices.join(',')}]''';
final MockProcess mockDevicesProcess =
MockProcess(stdout: output, stdoutEncoding: utf8);
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[
FakeProcessInfo(mockDevicesProcess, <String>['devices'])
];
}
test('fails if no platforms are provided', () async {
setMockFlutterDevicesOutput();
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Exactly one of'),
]),
);
});
test('fails if wasm flag is present but not web platform', () async {
setMockFlutterDevicesOutput();
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--android', '--wasm'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('--wasm is only supported on the web platform'),
]),
);
});
test('fails if multiple platforms are provided', () async {
setMockFlutterDevicesOutput();
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--ios', '--macos'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Exactly one of'),
]),
);
});
test('fails for iOS if no iOS devices are present', () async {
setMockFlutterDevicesOutput(hasIOSDevice: false);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--ios'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No iOS devices'),
]),
);
});
test('handles flutter tool banners when checking devices', () async {
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/test_driver/integration_test.dart',
'example/integration_test/foo_test.dart',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
setMockFlutterDevicesOutput(includeBanner: true);
final List<String> output =
await runCapturingPrint(runner, <String>['drive-examples', '--ios']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
});
test('fails for iOS if getting devices fails', () async {
// Simulate failure from `flutter devices`.
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1), <String>['devices'])
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--ios'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No iOS devices'),
]),
);
});
test('fails for Android if no Android devices are present', () async {
setMockFlutterDevicesOutput(hasAndroidDevice: false);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No Android devices'),
]),
);
});
test('a plugin without any integration test files is reported as an error',
() async {
setMockFlutterDevicesOutput();
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/lib/main.dart',
'example/android/android.java',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No driver tests were run (1 example(s) found).'),
contains('No tests ran'),
]),
);
});
test('integration tests using test(...) fail validation', () async {
setMockFlutterDevicesOutput();
final RepositoryPackage package = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/test_driver/integration_test.dart',
'example/integration_test/foo_test.dart',
'example/android/android.java',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
package.directory
.childDirectory('example')
.childDirectory('integration_test')
.childFile('foo_test.dart')
.writeAsStringSync('''
test('this is the wrong kind of test!'), () {
...
}
''');
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('foo_test.dart failed validation'),
]),
);
});
test('tests an iOS plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/bar_test.dart',
'example/integration_test/foo_test.dart',
'example/integration_test/ignore_me.dart',
'example/android/android.java',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
setMockFlutterDevicesOutput();
final List<String> output =
await runCapturingPrint(runner, <String>['drive-examples', '--ios']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeIOSDevice,
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('handles missing CI debug logs directory', () async {
mockPlatform.environment.remove('FLUTTER_LOGS_DIR');
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/bar_test.dart',
'example/integration_test/foo_test.dart',
'example/integration_test/ignore_me.dart',
'example/android/android.java',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
setMockFlutterDevicesOutput();
final List<String> output =
await runCapturingPrint(runner, <String>['drive-examples', '--ios']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeIOSDevice,
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('saves a screenshot if test is taking too long', () async {
setMockFlutterDevicesOutput();
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/bar_test.dart',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
final FakeAsync fakeAsync = FakeAsync();
processRunner.mockProcessesForExecutable['flutter']!
.addAll(<FakeProcessInfo>[
FakeProcessInfo(
_FakeDelayingProcess(
delayDuration: const Duration(minutes: 11),
fakeAsync: fakeAsync),
<String>['test']),
FakeProcessInfo(MockProcess(), <String>['screenshot']),
]);
final Directory pluginExampleDirectory = getExampleDir(plugin);
List<String> output = <String>[];
fakeAsync.run((_) {
() async {
output = await runCapturingPrint(
runner, <String>['drive-examples', '--ios']);
}();
});
fakeAsync.flushTimers();
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains(
'Test is taking a long time, taking screenshot test-timeout-screenshot_integration_test.png...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeIOSDevice,
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path,
),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'screenshot',
'-d',
_fakeIOSDevice,
'--out=/path/to/logs/test-timeout-screenshot_integration_test.png',
],
pluginExampleDirectory.path,
),
]));
});
test('driving when plugin does not support Linux is a no-op', () async {
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/integration_test/plugin_test.dart',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--linux',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Skipping unsupported platform linux...'),
contains('No issues found!'),
]),
);
// Output should be empty since running drive-examples --linux on a non-Linux
// plugin is a no-op.
expect(processRunner.recordedCalls, <ProcessCall>[]);
});
test('tests a Linux plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/linux/linux.cc',
],
platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--linux',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'linux',
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('driving when plugin does not suppport macOS is a no-op', () async {
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/integration_test/plugin_test.dart',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--macos',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Skipping unsupported platform macos...'),
contains('No issues found!'),
]),
);
// Output should be empty since running drive-examples --macos with no macos
// implementation is a no-op.
expect(processRunner.recordedCalls, <ProcessCall>[]);
});
test('tests a macOS plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/macos/macos.swift',
],
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--macos',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'macos',
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
// This tests the workaround for https://github.com/flutter/flutter/issues/135673
// and the behavior it tests should be removed once that is fixed.
group('runs tests separately on desktop', () {
test('macOS', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/first_test.dart',
'example/integration_test/second_test.dart',
'example/macos/macos.swift',
],
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--macos',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'macos',
'--debug-logs-dir=/path/to/logs',
'integration_test/first_test.dart',
],
pluginExampleDirectory.path),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'macos',
'--debug-logs-dir=/path/to/logs',
'integration_test/second_test.dart',
],
pluginExampleDirectory.path),
]));
});
// This tests the workaround for https://github.com/flutter/flutter/issues/135673
// and the behavior it tests should be removed once that is fixed.
test('Linux', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/first_test.dart',
'example/integration_test/second_test.dart',
'example/linux/foo.cc',
],
platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--linux',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'linux',
'--debug-logs-dir=/path/to/logs',
'integration_test/first_test.dart',
],
pluginExampleDirectory.path),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'linux',
'--debug-logs-dir=/path/to/logs',
'integration_test/second_test.dart',
],
pluginExampleDirectory.path),
]));
});
// This tests the workaround for https://github.com/flutter/flutter/issues/135673
// and the behavior it tests should be removed once that is fixed.
test('Windows', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/first_test.dart',
'example/integration_test/second_test.dart',
'example/windows/foo.cpp',
],
platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--windows',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'windows',
'--debug-logs-dir=/path/to/logs',
'integration_test/first_test.dart',
],
pluginExampleDirectory.path),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'windows',
'--debug-logs-dir=/path/to/logs',
'integration_test/second_test.dart',
],
pluginExampleDirectory.path),
]));
});
});
test('driving when plugin does not suppport web is a no-op', () async {
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/integration_test/plugin_test.dart',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
// Output should be empty since running drive-examples --web on a non-web
// plugin is a no-op.
expect(processRunner.recordedCalls, <ProcessCall>[]);
});
test('drives a web plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/plugin_test.dart',
],
pluginExampleDirectory.path),
]));
});
test('drives a web plugin compiled to WASM', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
'--wasm',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--wasm',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/plugin_test.dart',
],
pluginExampleDirectory.path),
]));
});
test('runs chromedriver when requested', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web', '--run-chromedriver']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
const ProcessCall('chromedriver', <String>['--port=4444'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/plugin_test.dart',
],
pluginExampleDirectory.path),
]));
});
test('drives a web plugin with CHROME_EXECUTABLE', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
mockPlatform.environment['CHROME_EXECUTABLE'] = '/path/to/chrome';
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--chrome-binary=/path/to/chrome',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/plugin_test.dart',
],
pluginExampleDirectory.path),
]));
});
test('driving when plugin does not suppport Windows is a no-op', () async {
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/integration_test/plugin_test.dart',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--windows',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Skipping unsupported platform windows...'),
contains('No issues found!'),
]),
);
// Output should be empty since running drive-examples --windows on a
// non-Windows plugin is a no-op.
expect(processRunner.recordedCalls, <ProcessCall>[]);
});
test('tests a Windows plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/windows/windows.cpp',
],
platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--windows',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
'windows',
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('tests an Android plugin', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/android/android.java',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
setMockFlutterDevicesOutput();
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--android',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeAndroidDevice,
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('tests an Android plugin with "apk" alias', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/android/android.java',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
setMockFlutterDevicesOutput();
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--apk',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeAndroidDevice,
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('driving when plugin does not support Android is no-op', () async {
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
],
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
},
);
setMockFlutterDevicesOutput();
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--android']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Skipping unsupported platform android...'),
contains('No issues found!'),
]),
);
// Output should be empty other than the device query.
expect(processRunner.recordedCalls, <ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
]);
});
test('driving when plugin does not support iOS is no-op', () async {
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
],
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
},
);
setMockFlutterDevicesOutput();
final List<String> output =
await runCapturingPrint(runner, <String>['drive-examples', '--ios']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Skipping unsupported platform ios...'),
contains('No issues found!'),
]),
);
// Output should be empty other than the device query.
expect(processRunner.recordedCalls, <ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
]);
});
test('platform interface plugins are silently skipped', () async {
createFakePlugin('aplugin_platform_interface', packagesDir,
examples: <String>[]);
setMockFlutterDevicesOutput();
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--macos']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for aplugin_platform_interface'),
contains(
'SKIPPING: Platform interfaces are not expected to have integration tests.'),
contains('No issues found!'),
]),
);
// Output should be empty since it's skipped.
expect(processRunner.recordedCalls, <ProcessCall>[]);
});
test('enable-experiment flag', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/plugin_test.dart',
'example/android/android.java',
'example/ios/ios.m',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
setMockFlutterDevicesOutput();
await runCapturingPrint(runner, <String>[
'drive-examples',
'--ios',
'--enable-experiment=exp1',
]);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeIOSDevice,
'--enable-experiment=exp1',
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
test('fails when no example is present', () async {
createFakePlugin(
'plugin',
packagesDir,
examples: <String>[],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No driver tests were run (0 example(s) found).'),
contains('The following packages had errors:'),
contains(' plugin:\n'
' No tests ran (use --exclude if this is intentional)'),
]),
);
});
test('web fails when no driver is present', () async {
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/bar_test.dart',
'example/integration_test/foo_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No driver found for plugin/example'),
contains('No driver tests were run (1 example(s) found).'),
contains('The following packages had errors:'),
contains(' plugin:\n'
' No tests ran (use --exclude if this is intentional)'),
]),
);
});
test('web fails when no integration tests are present', () async {
createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/test_driver/integration_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('No driver tests were run (1 example(s) found).'),
contains('The following packages had errors:'),
contains(' plugin:\n'
' No tests ran (use --exclude if this is intentional)'),
]),
);
});
test('"flutter drive" reports test failures', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/test_driver/integration_test.dart',
'example/integration_test/bar_test.dart',
'example/integration_test/foo_test.dart',
'example/web/index.html',
],
platformSupport: <String, PlatformDetails>{
platformWeb: const PlatformDetails(PlatformSupport.inline),
},
);
// Simulate failure from `flutter drive`.
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[
// Fail both bar_test.dart and foo_test.dart.
FakeProcessInfo(MockProcess(exitCode: 1), <String>['drive']),
FakeProcessInfo(MockProcess(exitCode: 1), <String>['drive']),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('The following packages had errors:'),
contains(' plugin:\n'
' example/integration_test/bar_test.dart\n'
' example/integration_test/foo_test.dart'),
]),
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/bar_test.dart',
],
pluginExampleDirectory.path),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/plugin_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/foo_test.dart',
],
pluginExampleDirectory.path),
]));
});
test('"flutter test" reports test failures', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/integration_test/bar_test.dart',
'example/integration_test/foo_test.dart',
'example/ios/ios.swift',
],
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
);
setMockFlutterDevicesOutput();
// Simulate failure from `flutter test`.
processRunner.mockProcessesForExecutable[getFlutterCommand(mockPlatform)]!
.add(FakeProcessInfo(MockProcess(exitCode: 1), <String>['test']));
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--ios'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('The following packages had errors:'),
contains(' plugin:\n'
' Integration tests failed.'),
]),
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(getFlutterCommand(mockPlatform),
const <String>['devices', '--machine'], null),
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'test',
'-d',
_fakeIOSDevice,
'--debug-logs-dir=/path/to/logs',
'integration_test',
],
pluginExampleDirectory.path),
]));
});
group('packages', () {
test('can be driven', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, extraFiles: <String>[
'example/integration_test/foo_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
]);
final Directory exampleDirectory = getExampleDir(package);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_package'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/a_package_example-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/foo_test.dart'
],
exampleDirectory.path),
]));
});
test('drive handles missing CI screenshot directory', () async {
mockPlatform.environment.remove('FLUTTER_LOGS_DIR');
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, extraFiles: <String>[
'example/integration_test/foo_test.dart',
'example/test_driver/integration_test.dart',
'example/web/index.html',
]);
final Directory exampleDirectory = getExampleDir(package);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_package'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/foo_test.dart'
],
exampleDirectory.path),
]));
});
test('are skipped when example does not support platform', () async {
createFakePackage('a_package', packagesDir,
isFlutter: true,
extraFiles: <String>[
'example/integration_test/foo_test.dart',
'example/test_driver/integration_test.dart',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_package'),
contains('Skipping a_package/example; does not support any '
'requested platforms'),
contains('SKIPPING: No example supports requested platform(s).'),
]),
);
expect(processRunner.recordedCalls.isEmpty, true);
});
test('drive only supported examples if there is more than one', () async {
final RepositoryPackage package = createFakePackage(
'a_package', packagesDir,
isFlutter: true,
examples: <String>[
'with_web',
'without_web'
],
extraFiles: <String>[
'example/with_web/integration_test/foo_test.dart',
'example/with_web/test_driver/integration_test.dart',
'example/with_web/web/index.html',
'example/without_web/integration_test/foo_test.dart',
'example/without_web/test_driver/integration_test.dart',
]);
final Directory supportedExampleDirectory =
getExampleDir(package).childDirectory('with_web');
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_package'),
contains(
'Skipping a_package/example/without_web; does not support any requested platforms.'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>[
'drive',
'-d',
'web-server',
'--web-port=7357',
'--browser-name=chrome',
'--screenshot=/path/to/logs/a_package_example_with_web-drive',
'--driver',
'test_driver/integration_test.dart',
'--target',
'integration_test/foo_test.dart'
],
supportedExampleDirectory.path),
]));
});
test('are skipped when there is no integration testing', () async {
createFakePackage('a_package', packagesDir,
isFlutter: true, extraFiles: <String>['example/web/index.html']);
final List<String> output = await runCapturingPrint(runner, <String>[
'drive-examples',
'--web',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_package'),
contains(
'SKIPPING: No example is configured for integration tests.'),
]),
);
expect(processRunner.recordedCalls.isEmpty, true);
});
});
group('file filtering', () {
const List<String> files = <String>[
'pubspec.yaml',
'foo.dart',
'foo.java',
'foo.kt',
'foo.m',
'foo.swift',
'foo.cc',
'foo.cpp',
'foo.h',
];
for (final String file in files) {
test('runs command for changes to $file', () async {
createFakePackage('package_a', packagesDir);
gitProcessRunner.mockProcessesForExecutable['git-diff'] =
<FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: '''
packages/package_a/$file
''')),
];
// The target platform is irrelevant here; because this repo's
// packages are fully federated, there's no need to distinguish
// the ignore list by target (e.g., skipping iOS tests if only Java or
// Kotlin files change), because package-level filering will already
// accomplish the same goal.
final List<String> output = await runCapturingPrint(
runner, <String>['drive-examples', '--web']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for package_a'),
]));
});
}
test('skips commands if all files should be ignored', () async {
createFakePackage('package_a', packagesDir);
gitProcessRunner.mockProcessesForExecutable['git-diff'] =
<FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: '''
README.md
CODEOWNERS
packages/package_a/CHANGELOG.md
''')),
];
final List<String> output =
await runCapturingPrint(runner, <String>['drive-examples']);
expect(
output,
isNot(containsAllInOrder(<Matcher>[
contains('Running for package_a'),
])));
expect(
output,
containsAllInOrder(<Matcher>[
contains('SKIPPING ALL PACKAGES'),
]));
});
});
});
}
class _FakeDelayingProcess extends Fake implements io.Process {
/// Creates a mock process that takes [delayDuration] time to exit successfully.
_FakeDelayingProcess(
{required Duration delayDuration, required FakeAsync fakeAsync})
: _delayDuration = delayDuration,
_fakeAsync = fakeAsync;
final Duration _delayDuration;
final FakeAsync _fakeAsync;
@override
Future<int> get exitCode async {
_fakeAsync.elapse(_delayDuration);
return 0;
}
}