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