blob: d6ee60742049374336d00b9d803cf875b53e18ad [file] [log] [blame]
// 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 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import '../framework/devices.dart';
import '../framework/framework.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';
const String _packageName = 'package_with_native_assets';
const List<String> _buildModes = <String>[
'debug',
'profile',
'release',
];
TaskFunction createNativeAssetsTest({
String? deviceIdOverride,
bool checkAppRunningOnLocalDevice = true,
bool isIosSimulator = false,
}) {
return () async {
if (deviceIdOverride == null) {
final Device device = await devices.workingDevice;
await device.unlock();
deviceIdOverride = device.deviceId;
}
await enableNativeAssets();
for (final String buildMode in _buildModes) {
if (buildMode != 'debug' && isIosSimulator) {
continue;
}
final TaskResult buildModeResult = await inTempDir((Directory tempDirectory) async {
final Directory packageDirectory = await createTestProject(_packageName, tempDirectory);
final Directory exampleDirectory = dir(packageDirectory.uri.resolve('example/').toFilePath());
final List<String> options = <String>[
'-d',
deviceIdOverride!,
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
'--uninstall-first',
'--$buildMode',
];
int transitionCount = 0;
bool done = false;
await inDirectory<void>(exampleDirectory, () async {
final int runFlutterResult = await runFlutter(
options: options,
onLine: (String line, Process process) {
if (done) {
return;
}
switch (transitionCount) {
case 0:
if (!line.contains('Flutter run key commands.')) {
return;
}
if (buildMode == 'debug') {
// Do a hot reload diff on the initial dill file.
process.stdin.writeln('r');
} else {
done = true;
process.stdin.writeln('q');
}
case 1:
if (!line.contains('Reloaded')) {
return;
}
process.stdin.writeln('R');
case 2:
// Do a hot restart, pushing a new complete dill file.
if (!line.contains('Restarted application')) {
return;
}
// Do another hot reload, pushing a diff to the second dill file.
process.stdin.writeln('r');
case 3:
if (!line.contains('Reloaded')) {
return;
}
done = true;
process.stdin.writeln('q');
}
transitionCount += 1;
},
);
if (runFlutterResult != 0) {
print('Flutter run returned non-zero exit code: $runFlutterResult.');
}
});
final int expectedNumberOfTransitions = buildMode == 'debug' ? 4 : 1;
if (transitionCount != expectedNumberOfTransitions) {
return TaskResult.failure(
'Did not get expected number of transitions: $transitionCount '
'(expected $expectedNumberOfTransitions)',
);
}
return TaskResult.success(null);
});
if (buildModeResult.failed) {
return buildModeResult;
}
}
return TaskResult.success(null);
};
}
Future<int> runFlutter({
required List<String> options,
required void Function(String, Process) onLine,
}) async {
final Process process = await startFlutter(
'run',
options: options,
);
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen((String line) {
onLine(line, process);
print('stdout: $line');
}, onDone: stdoutDone.complete);
process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(
(String line) => print('stderr: $line'),
onDone: stderrDone.complete,
);
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
final int exitCode = await process.exitCode;
return exitCode;
}
final String _flutterBin = path.join(flutterDirectory.path, 'bin', 'flutter');
Future<void> enableNativeAssets() async {
print('Enabling configs for native assets...');
final int configResult = await exec(
_flutterBin,
<String>[
'config',
'-v',
'--enable-native-assets',
],
canFail: true);
if (configResult != 0) {
print('Failed to enable configuration, tasks may not run.');
}
}
Future<Directory> createTestProject(
String packageName,
Directory tempDirectory,
) async {
await exec(
_flutterBin,
<String>[
'create',
'--no-pub',
'--template=package_ffi',
packageName,
],
workingDirectory: tempDirectory.path,
);
final Directory packageDirectory = Directory(
path.join(tempDirectory.path, packageName),
);
await _pinDependencies(
File(path.join(packageDirectory.path, 'pubspec.yaml')),
);
await _pinDependencies(
File(path.join(packageDirectory.path, 'example', 'pubspec.yaml')),
);
await exec(
_flutterBin,
<String>[
'pub',
'get',
],
workingDirectory: packageDirectory.path,
);
return packageDirectory;
}
Future<void> _pinDependencies(File pubspecFile) async {
final String oldPubspec = await pubspecFile.readAsString();
final String newPubspec = oldPubspec.replaceAll(': ^', ': ');
await pubspecFile.writeAsString(newPubspec);
}
Future<T> inTempDir<T>(Future<T> Function(Directory tempDirectory) fun) async {
final Directory tempDirectory = dir(Directory.systemTemp.createTempSync().resolveSymbolicLinksSync());
try {
return await fun(tempDirectory);
} finally {
tempDirectory.deleteSync(recursive: true);
}
}