mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Partial work towards https://github.com/flutter/flutter/issues/132245. As far as I can tell, this just plumbs flags downwards and has no semantic changes.
215 lines
6.9 KiB
Dart
215 lines
6.9 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/local.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:process/process.dart';
|
|
import 'package:vm_service/vm_service.dart';
|
|
|
|
import '../src/common.dart';
|
|
import 'test_driver.dart';
|
|
|
|
/// The [FileSystem] for the integration test environment.
|
|
const FileSystem fileSystem = LocalFileSystem();
|
|
|
|
/// The [Platform] for the integration test environment.
|
|
const Platform platform = LocalPlatform();
|
|
|
|
/// The [ProcessManager] for the integration test environment.
|
|
const ProcessManager processManager = LocalProcessManager();
|
|
|
|
/// Creates a temporary directory but resolves any symlinks to return the real
|
|
/// underlying path to avoid issues with breakpoints/hot reload.
|
|
/// https://github.com/flutter/flutter/pull/21741
|
|
Directory createResolvedTempDirectorySync(String prefix) {
|
|
assert(prefix.endsWith('.'));
|
|
final Directory tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_$prefix');
|
|
return fileSystem.directory(tempDirectory.resolveSymbolicLinksSync());
|
|
}
|
|
|
|
void writeFile(String path, String content, {bool writeFutureModifiedDate = false}) {
|
|
final File file = fileSystem.file(path)
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(content, flush: true);
|
|
// Some integration tests on Windows to not see this file as being modified
|
|
// recently enough for the hot reload to pick this change up unless the
|
|
// modified time is written in the future.
|
|
if (writeFutureModifiedDate) {
|
|
file.setLastModifiedSync(DateTime.now().add(const Duration(seconds: 5)));
|
|
}
|
|
}
|
|
|
|
void writeBytesFile(String path, List<int> content) {
|
|
fileSystem.file(path)
|
|
..createSync(recursive: true)
|
|
..writeAsBytesSync(content, flush: true);
|
|
}
|
|
|
|
void writePackages(String folder) {
|
|
writeFile(fileSystem.path.join(folder, '.packages'), '''
|
|
test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/
|
|
''');
|
|
}
|
|
|
|
Future<void> getPackages(String folder) async {
|
|
final List<String> command = <String>[
|
|
fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'),
|
|
'pub',
|
|
'get',
|
|
];
|
|
final ProcessResult result = await processManager.run(command, workingDirectory: folder);
|
|
if (result.exitCode != 0) {
|
|
throw Exception('flutter pub get failed: ${result.stderr}\n${result.stdout}');
|
|
}
|
|
}
|
|
|
|
const String kLocalEngineEnvironment = 'FLUTTER_LOCAL_ENGINE';
|
|
const String kLocalEngineHostEnvironment = 'FLUTTER_LOCAL_ENGINE_HOST';
|
|
const String kLocalEngineLocation = 'FLUTTER_LOCAL_ENGINE_SRC_PATH';
|
|
|
|
List<String> getLocalEngineArguments() {
|
|
return <String>[
|
|
if (platform.environment.containsKey(kLocalEngineEnvironment))
|
|
'--local-engine=${platform.environment[kLocalEngineEnvironment]}',
|
|
if (platform.environment.containsKey(kLocalEngineLocation))
|
|
'--local-engine-src-path=${platform.environment[kLocalEngineLocation]}',
|
|
if (platform.environment.containsKey(kLocalEngineHostEnvironment))
|
|
'--local-engine-host=${platform.environment[kLocalEngineHostEnvironment]}',
|
|
];
|
|
}
|
|
|
|
Future<void> pollForServiceExtensionValue<T>({
|
|
required FlutterTestDriver testDriver,
|
|
required String extension,
|
|
required T continuePollingValue,
|
|
required Matcher matches,
|
|
String valueKey = 'value',
|
|
}) async {
|
|
for (int i = 0; i < 10; i++) {
|
|
final Response response = await testDriver.callServiceExtension(extension);
|
|
if (response.json?[valueKey] as T == continuePollingValue) {
|
|
await Future<void>.delayed(const Duration(seconds: 1));
|
|
} else {
|
|
expect(response.json?[valueKey] as T, matches);
|
|
return;
|
|
}
|
|
}
|
|
fail(
|
|
"Did not find expected value for service extension '$extension'. All call"
|
|
" attempts responded with '$continuePollingValue'.",
|
|
);
|
|
}
|
|
|
|
abstract final class AppleTestUtils {
|
|
static const List<String> requiredSymbols = <String>[
|
|
'_kDartIsolateSnapshotData',
|
|
'_kDartIsolateSnapshotInstructions',
|
|
'_kDartVmSnapshotData',
|
|
'_kDartVmSnapshotInstructions'
|
|
];
|
|
|
|
static List<String> getExportedSymbols(String dwarfPath) {
|
|
final ProcessResult nm = processManager.runSync(
|
|
<String>[
|
|
'nm',
|
|
'--debug-syms', // nm docs: 'Show all symbols, even debugger only'
|
|
'--defined-only',
|
|
'--just-symbol-name',
|
|
dwarfPath,
|
|
'-arch',
|
|
'arm64',
|
|
],
|
|
);
|
|
final String nmOutput = (nm.stdout as String).trim();
|
|
return nmOutput.isEmpty ? const <String>[] : nmOutput.split('\n');
|
|
}
|
|
}
|
|
|
|
/// Matcher to be used for [ProcessResult] returned
|
|
/// from a process run
|
|
///
|
|
/// The default for [exitCode] will be 0 while
|
|
/// [stdoutPattern] and [stderrPattern] are both optional
|
|
class ProcessResultMatcher extends Matcher {
|
|
const ProcessResultMatcher({
|
|
this.exitCode = 0,
|
|
this.stdoutPattern,
|
|
this.stderrPattern,
|
|
});
|
|
|
|
/// The expected exit code to get returned from a process run
|
|
final int exitCode;
|
|
|
|
/// Substring to find in the process's stdout
|
|
final Pattern? stdoutPattern;
|
|
|
|
/// Substring to find in the process's stderr
|
|
final Pattern? stderrPattern;
|
|
|
|
@override
|
|
Description describe(Description description) {
|
|
description.add('a process with exit code $exitCode');
|
|
if (stdoutPattern != null) {
|
|
description.add(' and stdout: "$stdoutPattern"');
|
|
}
|
|
if (stderrPattern != null) {
|
|
description.add(' and stderr: "$stderrPattern"');
|
|
}
|
|
|
|
return description;
|
|
}
|
|
|
|
@override
|
|
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
|
|
final ProcessResult result = item as ProcessResult;
|
|
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 (stderrPattern != null) {
|
|
foundStderr = stderr.contains(stderrPattern!);
|
|
matchState['stderr'] = stderr;
|
|
} else if (stderr.isNotEmpty) {
|
|
matchState['stderr'] = stderr;
|
|
}
|
|
|
|
return result.exitCode == exitCode && foundStdout && foundStderr;
|
|
}
|
|
|
|
@override
|
|
Description describeMismatch(
|
|
Object? item,
|
|
Description mismatchDescription,
|
|
Map<dynamic, dynamic> matchState,
|
|
bool verbose,
|
|
) {
|
|
final ProcessResult result = item! as ProcessResult;
|
|
|
|
if (result.exitCode != exitCode) {
|
|
mismatchDescription.add('Actual exitCode was ${result.exitCode}\n');
|
|
}
|
|
|
|
if (matchState.containsKey('stdout')) {
|
|
mismatchDescription.add('Actual stdout:\n${matchState["stdout"]}\n');
|
|
}
|
|
|
|
if (matchState.containsKey('stderr')) {
|
|
mismatchDescription.add('Actual stderr:\n${matchState["stderr"]}\n');
|
|
}
|
|
|
|
return mismatchDescription;
|
|
}
|
|
}
|