blob: c1ca3399439257311958b03d85f8b47442138693 [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart=2.18
import 'dart:io';
import 'package:native_assets_cli/code_assets_builder.dart';
import 'package:test/test.dart';
import '../utils.dart';
import 'helpers.dart';
String usingTargetOSMessageForPlatform(String targetOS) =>
'Specializing Platform getters for target OS $targetOS.';
final String usingTargetOSMessage =
usingTargetOSMessageForPlatform(Platform.operatingSystem);
String crossOSNotAllowedError(String format) =>
"'dart build -f $format' does not support cross-OS compilation.";
final String hostOSMessage = 'Host OS: ${Platform.operatingSystem}';
String targetOSMessage(String targetOS) => 'Target OS: $targetOS';
void main(List<String> args) async {
if (!nativeAssetsExperimentAvailableOnCurrentChannel) {
return;
}
final bool fromDartdevSource = args.contains('--source');
final hostOS = Platform.operatingSystem;
final crossOS = Platform.isLinux ? 'macos' : 'linux';
for (final targetOS in [null, hostOS, crossOS]) {
String? osModifier;
if (targetOS == hostOS) {
osModifier = 'host';
} else if (targetOS == crossOS) {
osModifier = 'cross';
}
for (final verbose in [true, false]) {
final testModifier = [
'',
if (osModifier != null) osModifier,
if (verbose) 'verbose'
].join(' ');
test('dart build$testModifier', timeout: longTimeout, () async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
final bool expectCrossOSFailure = targetOS == crossOS;
final result = await runDart(
arguments: [
'--enable-experiment=native-assets',
if (fromDartdevSource)
Platform.script.resolve('../../bin/dartdev.dart').toFilePath(),
'build',
if (targetOS != null) ...[
'--target-os',
targetOS,
],
if (verbose) '-v',
'bin/dart_app.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: !expectCrossOSFailure,
);
if (expectCrossOSFailure) {
expect(result.stderr, contains(crossOSNotAllowedError('exe')));
expect(result.stderr, contains(hostOSMessage));
expect(result.stderr, contains(targetOSMessage(crossOS)));
expect(result.exitCode, 128);
return; // No executable to run.
}
if (verbose) {
expect(result.stdout, contains(usingTargetOSMessage));
expect(result.stdout, contains('build.dart'));
} else {
expect(result.stdout, isNot(contains('build.dart')));
}
final relativeExeUri = Uri.file('./bin/dart_app/dart_app.exe');
final absoluteExeUri = dartAppUri.resolveUri(relativeExeUri);
expect(await File.fromUri(absoluteExeUri).exists(), true);
await _withTempDir((tempUri) async {
// The link needs to have the same extension as the executable on
// Windows to be able to be executable.
final link = Link.fromUri(tempUri.resolve('my_link.exe'));
await link.create(absoluteExeUri.toFilePath());
for (final exeUri in [
absoluteExeUri,
relativeExeUri,
link.uri,
]) {
final result = await runProcess(
executable: exeUri,
arguments: [],
workingDirectory: dartAppUri,
logger: logger,
);
expectDartAppStdout(result.stdout);
}
});
});
});
}
}
test('dart build native assets build failure', timeout: longTimeout,
() async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
final buildDotDart = dartAppUri.resolve('../native_add/hook/build.dart');
await File.fromUri(buildDotDart).writeAsString('''
void main(List<String> args) {
throw UnimplementedError();
}
''');
final result = await runDart(
arguments: [
'--enable-experiment=native-assets',
'build',
'bin/dart_app.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.stderr,
contains(
'Native assets build failed.',
),
);
expect(result.exitCode, 255);
});
});
test('dart build and link dylib conflict', timeout: longTimeout, () async {
await nativeAssetsTest('native_add_duplicate', (dartAppUri) async {
final result = await runDart(
arguments: [
'--enable-experiment=native-assets',
'build',
'bin/native_add_duplicate.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.stderr,
contains(
'Duplicate dynamic library file name',
),
);
expect(result.exitCode, 255);
});
});
test('dart link assets', timeout: longTimeout, () async {
await nativeAssetsTest('drop_dylib_link', (dartAppUri) async {
await runDart(
arguments: [
'--enable-experiment=native-assets',
'build',
'bin/drop_dylib_link.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
// Check that the build directory exists
final directory =
Directory.fromUri(dartAppUri.resolve('bin/drop_dylib_link'));
expect(directory.existsSync(), true);
// Check that only one dylib is in the final application package
final buildFiles = directory.listSync(recursive: true);
expect(
buildFiles.where((file) => file.path.contains('add')),
isNotEmpty,
);
expect(
buildFiles.where((file) => file.path.contains('multiply')),
isEmpty,
);
});
});
test('dart link assets', timeout: longTimeout, () async {
await nativeAssetsTest('add_asset_link', (dartAppUri) async {
final result = await runDart(
arguments: [
'--enable-experiment=native-assets',
'build',
'bin/add_asset_link.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.exitCode,
0, // linking is enabled so the build hook and link hook succeed.
);
// Check that the build directory exists
final directory =
Directory.fromUri(dartAppUri.resolve('bin/add_asset_link'));
expect(directory.existsSync(), true);
final dylib = OS.current.libraryFileName('add', DynamicLoadingBundled());
expect(
File.fromUri(directory.uri.resolve('lib/$dylib')).existsSync(),
true,
);
});
});
test('do not delete project', () async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
final result = await runDart(
arguments: [
'--enable-experiment=native-assets',
if (fromDartdevSource)
Platform.script.resolve('../../bin/dartdev.dart').toFilePath(),
'build',
'bin/dart_app.dart',
'.'
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.exitCode,
isNot(0), // The dartdev error code.
);
});
});
for (var filename in [
'drop_dylib_recording_calls',
'drop_dylib_recording_instances',
]) {
test('Tree-shaking in $filename: An asset is dropped', timeout: longTimeout,
() async {
await recordUseTest('drop_dylib_recording', (dartAppUri) async {
final addLib =
OS.current.libraryFileName('add', DynamicLoadingBundled());
final mulitplyLib =
OS.current.libraryFileName('multiply', DynamicLoadingBundled());
// Now try using the add symbol only, so the multiply library is
// tree-shaken.
await runDart(
arguments: [
'--enable-experiment=native-assets,record-use',
'build',
'bin/$filename.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
await runProcess(
executable: Uri.file('bin/$filename/$filename.exe'),
logger: logger,
expectedExitCode: 0,
throwOnUnexpectedExitCode: true,
workingDirectory: dartAppUri,
);
// The build directory exists
final shakeDirectory =
Directory.fromUri(dartAppUri.resolve('bin/$filename'));
expect(shakeDirectory.existsSync(), true);
// The multiply asset has been treeshaken
expect(
File.fromUri(shakeDirectory.uri.resolve('lib/$addLib')).existsSync(),
true,
);
expect(
File.fromUri(shakeDirectory.uri.resolve('lib/$mulitplyLib'))
.existsSync(),
false,
);
});
});
}
test(
'dart build with native dynamic linking',
timeout: longTimeout,
() async {
await nativeAssetsTest('native_dynamic_linking', (packageUri) async {
await runDart(
arguments: [
'--enable-experiment=native-assets',
'build',
'bin/native_dynamic_linking.dart',
],
workingDirectory: packageUri,
logger: logger,
);
final outputDirectory =
Directory.fromUri(packageUri.resolve('bin/native_dynamic_linking'));
expect(outputDirectory.existsSync(), true);
File dylibFile(String name) {
final libDirectoryUri = (outputDirectory.uri.resolve('lib/'));
final dylibBasename =
OS.current.libraryFileName(name, DynamicLoadingBundled());
return File.fromUri(libDirectoryUri.resolve(dylibBasename));
}
expect(dylibFile('add').existsSync(), true);
expect(dylibFile('math').existsSync(), true);
expect(dylibFile('debug').existsSync(), true);
final proccessResult = await runProcess(
executable: outputDirectory.uri.resolve('native_dynamic_linking.exe'),
logger: logger,
throwOnUnexpectedExitCode: true,
);
expect(proccessResult.stdout, contains('42'));
});
},
);
}
Future<void> _withTempDir(Future<void> Function(Uri tempUri) fun) async {
final tempDir = await Directory.systemTemp.createTemp('link_dir');
final tempDirResolved = Directory(await tempDir.resolveSymbolicLinks());
try {
await fun(tempDirResolved.uri);
} finally {
if (!Platform.environment.containsKey(keepTempKey) ||
Platform.environment[keepTempKey]!.isEmpty) {
await tempDirResolved.delete(recursive: true);
}
}
}