Unverified Commit 7f1f7655 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tools] Use process matcher for multidex test (#127996)

Part of https://github.com/flutter/flutter/issues/127135
Part of https://github.com/flutter/flutter/issues/125115
parent 420f442e
...@@ -32,10 +32,11 @@ HostPlatform _identifyMacBinaryArch(String path) { ...@@ -32,10 +32,11 @@ HostPlatform _identifyMacBinaryArch(String path) {
<String>['file', _dartBinary.path], <String>['file', _dartBinary.path],
); );
expect( expect(
result, result,
ProcessResultMatcher( ProcessResultMatcher(
stdoutSubstring: '${_dartBinary.path}: Mach-O 64-bit executable', stdoutPattern: '${_dartBinary.path}: Mach-O 64-bit executable',
)); ),
);
final RegExpMatch? match = pattern.firstMatch(result.stdout as String); final RegExpMatch? match = pattern.firstMatch(result.stdout as String);
if (match == null) { if (match == null) {
fail('Unrecognized STDOUT from `file`: "${result.stdout}"'); fail('Unrecognized STDOUT from `file`: "${result.stdout}"');
......
...@@ -44,18 +44,22 @@ void main() { ...@@ -44,18 +44,22 @@ void main() {
); );
// Pre-cache iOS engine Flutter.xcframework artifacts. // Pre-cache iOS engine Flutter.xcframework artifacts.
processManager.runSync(<String>[ ProcessResult result = processManager.runSync(
flutterBin, <String>[
...getLocalEngineArguments(), flutterBin,
'precache', ...getLocalEngineArguments(),
'--ios', 'precache',
], workingDirectory: tempDir.path); '--ios',
],
workingDirectory: tempDir.path,
);
expect(result, const ProcessResultMatcher());
// Pretend the SDK was on an external drive with stray "._" files in the xcframework // Pretend the SDK was on an external drive with stray "._" files in the xcframework
hiddenFile = xcframeworkArtifact.childFile('._Info.plist')..createSync(); hiddenFile = xcframeworkArtifact.childFile('._Info.plist')..createSync();
// Test a plugin example app to allow plugins validation. // Test a plugin example app to allow plugins validation.
processManager.runSync(<String>[ result = processManager.runSync(<String>[
flutterBin, flutterBin,
...getLocalEngineArguments(), ...getLocalEngineArguments(),
'create', 'create',
...@@ -65,6 +69,7 @@ void main() { ...@@ -65,6 +69,7 @@ void main() {
'plugin', 'plugin',
'hello', 'hello',
], workingDirectory: tempDir.path); ], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher());
pluginRoot = tempDir.childDirectory('hello'); pluginRoot = tempDir.childDirectory('hello');
projectRoot = pluginRoot.childDirectory('example').path; projectRoot = pluginRoot.childDirectory('example').path;
......
...@@ -9,6 +9,7 @@ import 'package:flutter_tools/src/globals.dart' as globals; ...@@ -9,6 +9,7 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/test_flutter_command_runner.dart'; import '../src/test_flutter_command_runner.dart';
import 'test_utils.dart';
void main() { void main() {
group('pass analyze template:', () { group('pass analyze template:', () {
...@@ -38,7 +39,7 @@ void main() { ...@@ -38,7 +39,7 @@ void main() {
final ProcessResult result = await globals.processManager final ProcessResult result = await globals.processManager
.run(<String>['flutter', 'analyze'], workingDirectory: projectPath); .run(<String>['flutter', 'analyze'], workingDirectory: projectPath);
expect(result.exitCode, 0); expect(result, const ProcessResultMatcher());
}); });
} }
}); });
......
...@@ -30,10 +30,10 @@ void main() { ...@@ -30,10 +30,10 @@ void main() {
'--target-platform=android-arm64', '--target-platform=android-arm64',
], workingDirectory: workingDirectory); ], workingDirectory: workingDirectory);
printOnFailure('Output of flutter build apk:'); expect(
printOnFailure(result.stdout.toString()); result,
printOnFailure(result.stderr.toString()); const ProcessResultMatcher(stdoutPattern: 'app-release.apk (total compressed)'),
expect(result.stdout.toString(), contains('app-release.apk (total compressed)')); );
final String line = result.stdout.toString() final String line = result.stdout.toString()
.split('\n') .split('\n')
...@@ -49,8 +49,6 @@ void main() { ...@@ -49,8 +49,6 @@ void main() {
final String commandArguments = devToolsCommand.split(runDevToolsMessage).last.trim(); final String commandArguments = devToolsCommand.split(runDevToolsMessage).last.trim();
final String relativeAppSizePath = outputFilePath.split('.flutter-devtools/').last.trim(); final String relativeAppSizePath = outputFilePath.split('.flutter-devtools/').last.trim();
expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue);
expect(result.exitCode, 0);
}); });
testWithoutContext('--analyze-size flag produces expected output on hello_world for iOS', () async { testWithoutContext('--analyze-size flag produces expected output on hello_world for iOS', () async {
...@@ -68,10 +66,10 @@ void main() { ...@@ -68,10 +66,10 @@ void main() {
'--no-codesign', '--no-codesign',
], workingDirectory: workingDirectory); ], workingDirectory: workingDirectory);
printOnFailure('Output of flutter build ios:'); expect(
printOnFailure(result.stdout.toString()); result,
printOnFailure(result.stderr.toString()); const ProcessResultMatcher(stdoutPattern: 'Dart AOT symbols accounted decompressed size'),
expect(result.stdout.toString(), contains('Dart AOT symbols accounted decompressed size')); );
final String line = result.stdout.toString() final String line = result.stdout.toString()
.split('\n') .split('\n')
...@@ -88,7 +86,6 @@ void main() { ...@@ -88,7 +86,6 @@ void main() {
expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue);
expect(codeSizeDir.existsSync(), true); expect(codeSizeDir.existsSync(), true);
expect(result.exitCode, 0);
tempDir.deleteSync(recursive: true); tempDir.deleteSync(recursive: true);
}, skip: !platform.isMacOS); // [intended] iOS can only be built on macos. }, skip: !platform.isMacOS); // [intended] iOS can only be built on macos.
...@@ -105,6 +102,11 @@ void main() { ...@@ -105,6 +102,11 @@ void main() {
'--enable-macos-desktop', '--enable-macos-desktop',
], workingDirectory: workingDirectory); ], workingDirectory: workingDirectory);
expect(
configResult,
const ProcessResultMatcher(),
);
printOnFailure('Output of flutter config:'); printOnFailure('Output of flutter config:');
printOnFailure(configResult.stdout.toString()); printOnFailure(configResult.stdout.toString());
printOnFailure(configResult.stderr.toString()); printOnFailure(configResult.stderr.toString());
...@@ -117,10 +119,10 @@ void main() { ...@@ -117,10 +119,10 @@ void main() {
'--code-size-directory=${codeSizeDir.path}', '--code-size-directory=${codeSizeDir.path}',
], workingDirectory: workingDirectory); ], workingDirectory: workingDirectory);
printOnFailure('Output of flutter build macos:'); expect(
printOnFailure(result.stdout.toString()); result,
printOnFailure(result.stderr.toString()); const ProcessResultMatcher(stdoutPattern: 'Dart AOT symbols accounted decompressed size'),
expect(result.stdout.toString(), contains('Dart AOT symbols accounted decompressed size')); );
final String line = result.stdout.toString() final String line = result.stdout.toString()
.split('\n') .split('\n')
...@@ -137,7 +139,6 @@ void main() { ...@@ -137,7 +139,6 @@ void main() {
expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue);
expect(codeSizeDir.existsSync(), true); expect(codeSizeDir.existsSync(), true);
expect(result.exitCode, 0);
tempDir.deleteSync(recursive: true); tempDir.deleteSync(recursive: true);
}, skip: !platform.isMacOS); // [intended] this is a macos only test. }, skip: !platform.isMacOS); // [intended] this is a macos only test.
...@@ -152,13 +153,13 @@ void main() { ...@@ -152,13 +153,13 @@ void main() {
'--target-platform=android-arm64', '--target-platform=android-arm64',
'--debug', '--debug',
], workingDirectory: fileSystem.path.join(getFlutterRoot(), 'examples', 'hello_world')); ], workingDirectory: fileSystem.path.join(getFlutterRoot(), 'examples', 'hello_world'));
expect(
printOnFailure('Output of flutter build apk:'); result,
printOnFailure(result.stdout.toString()); const ProcessResultMatcher(
printOnFailure(result.stderr.toString()); exitCode: 1,
expect(result.stderr.toString(), contains('"--analyze-size" can only be used on release builds')); stderrPattern: '"--analyze-size" can only be used on release builds',
),
expect(result.exitCode, 1); );
}); });
testWithoutContext('--analyze-size is not supported in combination with --split-debug-info', () async { testWithoutContext('--analyze-size is not supported in combination with --split-debug-info', () async {
...@@ -177,14 +178,13 @@ void main() { ...@@ -177,14 +178,13 @@ void main() {
final ProcessResult result = final ProcessResult result =
await processManager.run(command, workingDirectory: workingDirectory); await processManager.run(command, workingDirectory: workingDirectory);
printOnFailure('workingDirectory: $workingDirectory'); expect(
printOnFailure('command:\n${command.join(" ")}'); result,
printOnFailure('stdout:\n${result.stdout}'); const ProcessResultMatcher(
printOnFailure('stderr:\n${result.stderr}'); exitCode: 1,
stderrPattern: '"--analyze-size" cannot be combined with "--split-debug-info"',
expect(result.stderr.toString(), contains('"--analyze-size" cannot be combined with "--split-debug-info"')); ),
);
expect(result.exitCode, 1);
}); });
testWithoutContext('--analyze-size allows overriding the directory for code size files', () async { testWithoutContext('--analyze-size allows overriding the directory for code size files', () async {
...@@ -211,15 +211,14 @@ void main() { ...@@ -211,15 +211,14 @@ void main() {
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
); );
printOnFailure('workingDirectory: $workingDirectory'); expect(
printOnFailure('command:\n${command.join(" ")}'); result,
printOnFailure('stdout:\n${result.stdout}'); const ProcessResultMatcher(),
printOnFailure('stderr:\n${result.stderr}'); );
expect(result.exitCode, 0); expect(tempDir, exists);
expect(tempDir.existsSync(), true); expect(tempDir.childFile('snapshot.arm64-v8a.json'), exists);
expect(tempDir.childFile('snapshot.arm64-v8a.json').existsSync(), true); expect(tempDir.childFile('trace.arm64-v8a.json'), exists);
expect(tempDir.childFile('trace.arm64-v8a.json').existsSync(), true);
tempDir.deleteSync(recursive: true); tempDir.deleteSync(recursive: true);
}); });
......
...@@ -79,7 +79,7 @@ void main() { ...@@ -79,7 +79,7 @@ void main() {
'--verbose', '--verbose',
], workingDirectory: exampleDir.path); ], workingDirectory: exampleDir.path);
expect(result, ProcessResultMatcher()); expect(result, const ProcessResultMatcher());
final String exampleAppApk = fileSystem.path.join( final String exampleAppApk = fileSystem.path.join(
exampleDir.path, exampleDir.path,
......
...@@ -36,8 +36,7 @@ void main() { ...@@ -36,8 +36,7 @@ void main() {
'--debug', '--debug',
], workingDirectory: tempDir.path); ], workingDirectory: tempDir.path);
expect(result.exitCode, 0); expect(result, const ProcessResultMatcher(stdoutPattern: 'app-debug.apk'));
expect(result.stdout.toString(), contains('app-debug.apk'));
}); });
testWithoutContext('simple build apk without FlutterMultiDexApplication fails', () async { testWithoutContext('simple build apk without FlutterMultiDexApplication fails', () async {
...@@ -52,8 +51,8 @@ void main() { ...@@ -52,8 +51,8 @@ void main() {
'--debug', '--debug',
], workingDirectory: tempDir.path); ], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher(exitCode: 1));
expect(result.stderr.toString(), contains('Cannot fit requested classes in a single dex file')); expect(result.stderr.toString(), contains('Cannot fit requested classes in a single dex file'));
expect(result.stderr.toString(), contains('The number of method references in a .dex file cannot exceed 64K.')); expect(result.stderr.toString(), contains('The number of method references in a .dex file cannot exceed 64K.'));
expect(result.exitCode, 1);
}); });
} }
...@@ -32,21 +32,27 @@ final List<String> integrationTestExtraArgs = <String>['-d', 'flutter-tester']; ...@@ -32,21 +32,27 @@ final List<String> integrationTestExtraArgs = <String>['-d', 'flutter-tester'];
void main() { void main() {
setUpAll(() async { setUpAll(() async {
await processManager.run( expect(
<String>[ await processManager.run(
flutterBin, <String>[
'pub', flutterBin,
'get', 'pub',
], 'get',
workingDirectory: flutterTestDirectory ],
workingDirectory: flutterTestDirectory
),
const ProcessResultMatcher(),
); );
await processManager.run( expect(
<String>[ await processManager.run(
flutterBin, <String>[
'pub', flutterBin,
'get', 'pub',
], 'get',
workingDirectory: missingDependencyDirectory ],
workingDirectory: missingDependencyDirectory
),
const ProcessResultMatcher(),
); );
}); });
...@@ -112,71 +118,109 @@ void main() { ...@@ -112,71 +118,109 @@ void main() {
}); });
testWithoutContext('flutter test should run a test when its name matches a regexp', () async { testWithoutContext('flutter test should run a test when its name matches a regexp', () async {
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest(
extraArguments: const <String>['--name', 'inc.*de']); 'filtering',
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); automatedTestsDirectory,
expect(result.exitCode, 0); flutterTestDirectory,
extraArguments: const <String>['--name', 'inc.*de'],
);
expect(
result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should run a test when its name contains a string', () async { testWithoutContext('flutter test should run a test when its name contains a string', () async {
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest(
extraArguments: const <String>['--plain-name', 'include']); 'filtering',
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); automatedTestsDirectory,
expect(result.exitCode, 0); flutterTestDirectory,
extraArguments: const <String>['--plain-name', 'include'],
);
expect(
result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should run a test with a given tag', () async { testWithoutContext('flutter test should run a test with a given tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest(
extraArguments: const <String>['--tags', 'include-tag']); 'filtering_tag',
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); automatedTestsDirectory,
expect(result.exitCode, 0); flutterTestDirectory,
extraArguments: const <String>['--tags', 'include-tag'],
);
expect(
result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should not run a test with excluded tag', () async { testWithoutContext('flutter test should not run a test with excluded tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--exclude-tags', 'exclude-tag']); extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(
expect(result.exitCode, 0); result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should run all tests when tags are unspecified', () async { testWithoutContext('flutter test should run all tests when tags are unspecified', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory); final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory);
expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); expect(
expect(result.exitCode, 1); result,
ProcessResultMatcher(
exitCode: 1,
stdoutPattern: RegExp(r'\+\d+ -1: Some tests failed\.'),
),
);
}); });
testWithoutContext('flutter test should run a widgetTest with a given tag', () async { testWithoutContext('flutter test should run a widgetTest with a given tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--tags', 'include-tag']); extraArguments: const <String>['--tags', 'include-tag']);
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(
expect(result.exitCode, 0); result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async { testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--exclude-tags', 'exclude-tag']); extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(
expect(result.exitCode, 0); result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async { testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory); final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory);
expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); expect(
expect(result.exitCode, 1); result,
ProcessResultMatcher(
exitCode: 1,
stdoutPattern: RegExp(r'\+\d+ -1: Some tests failed\.'),
),
);
}); });
testWithoutContext('flutter test should run a test with an exact name in URI format', () async { testWithoutContext('flutter test should run a test with an exact name in URI format', () async {
final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory,
query: 'full-name=exactTestName'); query: 'full-name=exactTestName');
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(
expect(result.exitCode, 0); result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should run a test by line number in URI format', () async { testWithoutContext('flutter test should run a test by line number in URI format', () async {
final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory,
query: 'line=11'); query: 'line=11');
expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(
expect(result.exitCode, 0); result,
ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')),
);
}); });
testWithoutContext('flutter test should test runs to completion', () async { testWithoutContext('flutter test should test runs to completion', () async {
...@@ -214,8 +258,8 @@ void main() { ...@@ -214,8 +258,8 @@ void main() {
}); });
testWithoutContext('flutter test should respect --serve-observatory', () async { testWithoutContext('flutter test should respect --serve-observatory', () async {
late final Process process; Process? process;
late final StreamSubscription<String> sub; StreamSubscription<String>? sub;
try { try {
process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory, process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--start-paused', '--serve-observatory']); extraArguments: const <String>['--start-paused', '--serve-observatory']);
...@@ -231,16 +275,16 @@ void main() { ...@@ -231,16 +275,16 @@ void main() {
final HttpClientRequest request = await client.getUrl(vmServiceUri); final HttpClientRequest request = await client.getUrl(vmServiceUri);
final HttpClientResponse response = await request.close(); final HttpClientResponse response = await request.close();
final String content = await response.transform(utf8.decoder).join(); final String content = await response.transform(utf8.decoder).join();
expect(content.contains('Dart VM Observatory'), true); expect(content, contains('Dart VM Observatory'));
} finally { } finally {
await sub.cancel(); await sub?.cancel();
process.kill(); process?.kill();
} }
}); });
testWithoutContext('flutter test should serve DevTools', () async { testWithoutContext('flutter test should serve DevTools', () async {
late final Process process; Process? process;
late final StreamSubscription<String> sub; StreamSubscription<String>? sub;
try { try {
process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory, process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--start-paused']); extraArguments: const <String>['--start-paused']);
...@@ -256,10 +300,10 @@ void main() { ...@@ -256,10 +300,10 @@ void main() {
final HttpClientRequest request = await client.getUrl(devToolsUri); final HttpClientRequest request = await client.getUrl(devToolsUri);
final HttpClientResponse response = await request.close(); final HttpClientResponse response = await request.close();
final String content = await response.transform(utf8.decoder).join(); final String content = await response.transform(utf8.decoder).join();
expect(content.contains('DevTools'), true); expect(content, contains('DevTools'));
} finally { } finally {
await sub.cancel(); await sub?.cancel();
process.kill(); process?.kill();
} }
}); });
} }
...@@ -285,7 +329,12 @@ Future<void> _testFile( ...@@ -285,7 +329,12 @@ Future<void> _testFile(
extraArguments: extraArguments, extraArguments: extraArguments,
); );
expect(exec.exitCode, exitCode); expect(
exec.exitCode,
exitCode,
reason: '"$testName" returned code ${exec.exitCode}\n\nstdout:\n'
'${exec.stdout}\nstderr:\n${exec.stderr}',
);
final List<String> output = (exec.stdout as String).split('\n'); final List<String> output = (exec.stdout as String).split('\n');
if (output.first.startsWith('Waiting for another flutter command to release the startup lock...')) { if (output.first.startsWith('Waiting for another flutter command to release the startup lock...')) {
output.removeAt(0); output.removeAt(0);
......
...@@ -128,35 +128,32 @@ abstract final class AppleTestUtils { ...@@ -128,35 +128,32 @@ abstract final class AppleTestUtils {
/// Matcher to be used for [ProcessResult] returned /// Matcher to be used for [ProcessResult] returned
/// from a process run /// from a process run
/// ///
/// The default for [expectedExitCode] will be 0 while /// The default for [exitCode] will be 0 while
/// [stdoutSubstring] and [stderrSubstring] are both optional /// [stdoutPattern] and [stderrPattern] are both optional
class ProcessResultMatcher extends Matcher { class ProcessResultMatcher extends Matcher {
ProcessResultMatcher({ const ProcessResultMatcher({
this.expectedExitCode = 0, this.exitCode = 0,
this.stdoutSubstring, this.stdoutPattern,
this.stderrSubstring, this.stderrPattern,
}); });
/// The expected exit code to get returned from a process run /// The expected exit code to get returned from a process run
final int expectedExitCode; final int exitCode;
/// Substring to find in the process's stdout /// Substring to find in the process's stdout
final String? stdoutSubstring; final Pattern? stdoutPattern;
/// Substring to find in the process's stderr /// Substring to find in the process's stderr
final String? stderrSubstring; final Pattern? stderrPattern;
bool _foundStdout = true;
bool _foundStderr = true;
@override @override
Description describe(Description description) { Description describe(Description description) {
description.add('a process with exit code $expectedExitCode'); description.add('a process with exit code $exitCode');
if (stdoutSubstring != null) { if (stdoutPattern != null) {
description.add(' and stdout: "$stdoutSubstring"'); description.add(' and stdout: "$stdoutPattern"');
} }
if (stderrSubstring != null) { if (stderrPattern != null) {
description.add(' and stderr: "$stderrSubstring"'); description.add(' and stderr: "$stderrPattern"');
} }
return description; return description;
...@@ -165,18 +162,27 @@ class ProcessResultMatcher extends Matcher { ...@@ -165,18 +162,27 @@ class ProcessResultMatcher extends Matcher {
@override @override
bool matches(dynamic item, Map<dynamic, dynamic> matchState) { bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
final ProcessResult result = item as ProcessResult; final ProcessResult result = item as ProcessResult;
bool foundStdout = true;
if (stdoutSubstring != null) { bool foundStderr = true;
_foundStdout = (result.stdout as String).contains(stdoutSubstring!);
matchState['stdout'] = result.stdout; final String stdout = result.stdout as String;
final String stderr = result.stderr as String;
if (stdoutPattern != null) {
foundStdout = stdout.contains(stdoutPattern!);
matchState['stdout'] = stdout;
} else if (stdout.isNotEmpty) {
// even if we were not asserting on stdout, show stdout for debug purposes
matchState['stdout'] = stdout;
} }
if (stderrSubstring != null) { if (stderrPattern != null) {
_foundStderr = (result.stderr as String).contains(stderrSubstring!); foundStderr = stderr.contains(stderrPattern!);
matchState['stderr'] = result.stderr; matchState['stderr'] = stderr;
} else if (stderr.isNotEmpty) {
matchState['stderr'] = stderr;
} }
return result.exitCode == expectedExitCode && _foundStdout && _foundStderr; return result.exitCode == exitCode && foundStdout && foundStderr;
} }
@override @override
...@@ -188,16 +194,16 @@ class ProcessResultMatcher extends Matcher { ...@@ -188,16 +194,16 @@ class ProcessResultMatcher extends Matcher {
) { ) {
final ProcessResult result = item! as ProcessResult; final ProcessResult result = item! as ProcessResult;
if (result.exitCode != expectedExitCode) { if (result.exitCode != exitCode) {
mismatchDescription.add('Actual exitCode was ${result.exitCode}'); mismatchDescription.add('Actual exitCode was ${result.exitCode}\n');
} }
if (matchState.containsKey('stdout')) { if (matchState.containsKey('stdout')) {
mismatchDescription.add('Actual stdout:\n${matchState["stdout"]}'); mismatchDescription.add('Actual stdout:\n${matchState["stdout"]}\n');
} }
if (matchState.containsKey('stderr')) { if (matchState.containsKey('stderr')) {
mismatchDescription.add('Actual stderr:\n${matchState["stderr"]}'); mismatchDescription.add('Actual stderr:\n${matchState["stderr"]}\n');
} }
return mismatchDescription; return mismatchDescription;
......
...@@ -20,8 +20,13 @@ void main() { ...@@ -20,8 +20,13 @@ void main() {
'debug', 'debug',
]); ]);
expect(result.exitCode, 1); expect(
expect(result.stderr, contains('PROJECT_DIR environment variable must be set to the location of Flutter project to be built.')); result,
const ProcessResultMatcher(
exitCode: 1,
stderrPattern: 'PROJECT_DIR environment variable must be set to the location of Flutter project to be built.',
),
);
}); });
testWithoutContext('tool_backend.dart exits if FLUTTER_ROOT is not set', () async { testWithoutContext('tool_backend.dart exits if FLUTTER_ROOT is not set', () async {
...@@ -37,8 +42,13 @@ void main() { ...@@ -37,8 +42,13 @@ void main() {
'PROJECT_DIR': examplePath, 'PROJECT_DIR': examplePath,
}, includeParentEnvironment: false); // Prevent FLUTTER_ROOT set by test environment from leaking }, includeParentEnvironment: false); // Prevent FLUTTER_ROOT set by test environment from leaking
expect(result.exitCode, 1); expect(
expect(result.stderr, contains('FLUTTER_ROOT environment variable must be set to the location of the Flutter SDK.')); result,
const ProcessResultMatcher(
exitCode: 1,
stderrPattern: 'FLUTTER_ROOT environment variable must be set to the location of the Flutter SDK.',
),
);
}); });
testWithoutContext('tool_backend.dart exits if local engine does not match build mode', () async { testWithoutContext('tool_backend.dart exits if local engine does not match build mode', () async {
...@@ -52,7 +62,12 @@ void main() { ...@@ -52,7 +62,12 @@ void main() {
'LOCAL_ENGINE': 'release_foo_bar', // Does not contain "debug", 'LOCAL_ENGINE': 'release_foo_bar', // Does not contain "debug",
}); });
expect(result.exitCode, 1); expect(
expect(result.stderr, contains("ERROR: Requested build with Flutter local engine at 'release_foo_bar'")); result,
const ProcessResultMatcher(
exitCode: 1,
stderrPattern: "ERROR: Requested build with Flutter local engine at 'release_foo_bar'",
),
);
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment