blob: a69f54f6effe622ad02c6ee082be5a738359dfe8 [file]
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:record_use/record_use.dart';
import 'package:test/test.dart';
import '../utils.dart';
import 'helpers.dart';
void main() async {
if (!nativeAssetsExperimentAvailableOnCurrentChannel) {
return;
}
test('dart compile not supported', timeout: longTimeout, () async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
final result = await runDart(
arguments: [
'compile',
'exe',
'bin/dart_app.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.stderr,
stringContainsInOrder([
"'dart compile' does not support build hooks, use 'dart build' instead.",
'Packages with build hooks:',
]),
);
expect(
result.stderr,
contains('native_add'),
);
expect(
result.stderr,
contains('native_subtract'),
);
expect(result.exitCode, 255);
});
});
test('dart compile from different directory', timeout: longTimeout, () async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
final tempUri = dartAppUri.parent;
final otherDirUri = tempUri.resolve('other_dir/');
await Directory.fromUri(otherDirUri).create();
final result = await runDart(
arguments: [
'compile',
'exe',
dartAppUri.resolve('bin/dart_app.dart').toFilePath(),
],
workingDirectory: otherDirUri,
logger: logger,
expectExitCodeZero: false,
);
expect(
result.stderr,
stringContainsInOrder([
"'dart compile' does not support build hooks, use 'dart build' instead.",
'Packages with build hooks:',
]),
);
expect(
result.stderr,
contains('native_add'),
);
expect(
result.stderr,
contains('native_subtract'),
);
expect(result.exitCode, 255);
});
});
test('dart compile only bails in bin/', timeout: longTimeout, () async {
await nativeAssetsTest('dart_app', (dartAppUri) async {
await runDart(
arguments: ['pub', 'get'],
workingDirectory: dartAppUri,
logger: logger,
);
// 1. Compiling in bin/ should fail.
final resultBin = await runDart(
arguments: [
'compile',
'exe',
'bin/dart_app.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: false,
);
expect(resultBin.exitCode, 255);
expect(
resultBin.stderr,
contains(
"'dart compile' does not support build hooks, use 'dart build' instead.",
),
);
// 2. Compiling outside bin/ should succeed (even if it might fail at runtime).
final otherFile = File.fromUri(dartAppUri.resolve('tool/other.dart'));
await otherFile.parent.create(recursive: true);
await otherFile.writeAsString('void main() { print("hello"); }');
final resultOther = await runDart(
arguments: [
'-v',
'compile',
'exe',
'tool/other.dart',
],
workingDirectory: dartAppUri,
logger: logger,
);
expect(resultOther.exitCode, 0);
final exePath = dartAppUri.resolve('tool/other.exe').toFilePath();
final resultRun = await runProcess(
executable: Uri.file(exePath),
logger: logger,
);
expect(resultRun.stdout, contains('hello'));
});
});
test('Recorded usages in dart2js', timeout: longTimeout, () async {
await recordUseTest('drop_data_asset', (dartAppUri) async {
await runDart(
arguments: ['pub', 'get'],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
// Now try using the add symbol only, so the multiply library is
// tree-shaken.
await runDart(
arguments: [
'compile',
'js',
'--enable-experiment=record-use',
'--write-resources',
'bin/drop_data_asset_calls.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
// The build directory exists
final recordedUsages = File.fromUri(
dartAppUri.resolve('out.js.resources.json'),
);
expect(recordedUsages.existsSync(), true);
final actualRecordedUsages = recordedUsages.readAsStringSync();
final u = Recordings.fromJson(
jsonDecode(actualRecordedUsages) as Map<String, Object?>,
);
printOnFailure(actualRecordedUsages);
final constArguments = u.constArgumentsFor(
Method(
'add',
Class(
'MyMath',
Library('package:drop_data_asset/src/drop_data_asset.dart'),
),
isInstanceMember: false,
),
);
expect(constArguments.length, 1);
expect(constArguments.first.namedArguments.isEmpty, true);
expect(constArguments.first.positionalArguments, const [
IntConstant(3),
IntConstant(4),
]);
});
});
// TODO(https://github.com/dart-lang/native/issues/2893): Implement instance
// support.
test(
'Recorded usages in dart2js - no instance support yet',
timeout: longTimeout,
() async {
await recordUseTest('drop_data_asset', (dartAppUri) async {
await runDart(
arguments: ['pub', 'get'],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
// Now try using the add symbol only, so the multiply library is
// tree-shaken.
await runDart(
arguments: [
'compile',
'js',
'--write-resources',
'bin/drop_data_asset_instances.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
// The build directory exists
final recordedUsages = File.fromUri(
dartAppUri.resolve('out.js.resources.json'),
);
expect(recordedUsages.existsSync(), true);
final actualRecordedUsages = recordedUsages.readAsStringSync();
final u = Recordings.fromJson(
jsonDecode(actualRecordedUsages) as Map<String, Object?>,
);
final constantsOf = u.constantsOf(
Class(
'RecordCallToC',
Library('package:drop_data_asset/src/drop_data_asset.dart'),
),
);
expect(constantsOf.length, 0);
});
},
);
for (final testCase in [
(
command: 'js',
flag: '--write-resources',
recordedUsagesPath: 'out.js.resources.json',
),
(
command: 'exe',
flag: '--recorded-uses=',
recordedUsagesPath: 'recorded_usages.json',
),
(
command: 'aot-snapshot',
flag: '--recorded-uses=',
recordedUsagesPath: 'recorded_usages.json',
),
(
command: 'wasm',
flag: '--recorded-uses=',
recordedUsagesPath: 'recorded_usages.json',
),
]) {
final command = testCase.command;
final flag = testCase.flag;
final recordedUsagesPath = testCase.recordedUsagesPath;
test('Recorded usages in $command', timeout: longTimeout, () async {
final testFunction = command == 'js'
? recordUseTest
: recordUseNoHooksTest;
await testFunction('drop_data_asset', (dartAppUri) async {
await runDart(
arguments: ['pub', 'get'],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
final recordedUsagesUri = dartAppUri.resolve(recordedUsagesPath);
final flagWithArg = flag.endsWith('=')
? '$flag${recordedUsagesUri.toFilePath()}'
: flag;
await runDart(
arguments: [
'compile',
command,
'--enable-experiment=record-use',
flagWithArg,
'bin/drop_data_asset_calls.dart',
],
workingDirectory: dartAppUri,
logger: logger,
expectExitCodeZero: true,
);
final recordedUsages = File.fromUri(recordedUsagesUri);
expect(recordedUsages.existsSync(), true);
final actualRecordedUsages = recordedUsages.readAsStringSync();
final u = Recordings.fromJson(
jsonDecode(actualRecordedUsages) as Map<String, Object?>,
);
expect(u.calls.isNotEmpty, true);
});
});
}
}
extension on Recordings {
List<CallWithArguments> constArgumentsFor(Definition definition) {
final result = <CallWithArguments>[];
for (final entry in calls.entries) {
if (entry.key.semanticEquals(definition)) {
for (final call in entry.value) {
if (call is CallWithArguments) {
result.add(call);
}
}
}
}
return result;
}
List<Constant> constantsOf(Definition definition) {
final result = <Constant>[];
for (final entry in instances.entries) {
if (entry.key.semanticEquals(definition)) {
for (final instance in entry.value) {
if (instance is InstanceConstantReference) {
result.add(instance.instanceConstant);
}
}
}
}
return result;
}
}