blob: 21315a08b7862aa86771955048abfa21c46c0f8f [file]
// Copyright (c) 2026, 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:dart2wasm/util.dart';
import 'package:path/path.dart' as path;
import 'package:record_use/record_use.dart';
import 'util.dart';
final Uri _pkgVmDir = Platform.script.resolve('../../vm/');
const Set<String> dart2wasmNotSupported = {};
Future<void> runTestCase(
Uri sourceFileUri,
Uri sourcePackageUri,
Uri packagesFileUri,
) async {
final bool isThrowsTest = sourceFileUri.path.contains('throws');
await withTempDir((String tempDir) async {
final recordedUsesFile = path.join(tempDir, 'recorded_usages.json');
final List<String> args = [
Platform.executable,
'compile',
'wasm',
'-O2',
'--packages=${packagesFileUri.toFilePath()}',
sourceFileUri.toFilePath(),
'-o',
path.join(tempDir, 'out.wasm'),
'--enable-deferred-loading',
'--extra-compiler-option=--recorded-uses=$recordedUsesFile',
];
if (isThrowsTest) {
final result = await Process.run(args.first, args.skip(1).toList());
if (result.exitCode == 0) {
throw 'Compilation succeeded for $sourceFileUri but was expected to fail.';
}
final errors = '${result.stdout}\n${result.stderr}';
if (sourceFileUri.path.contains('invalid_location')) {
if (!errors.contains('RecordUse') ||
!errors.contains('cannot be placed on this element')) {
throw 'Wrong error message for $sourceFileUri:\n$errors';
}
}
return;
}
await run(args);
final actualSemantic = Recordings.fromJson(
jsonDecode(File(recordedUsesFile).readAsStringSync()),
);
final goldenFile = File('${sourceFileUri.toFilePath()}.json.expect');
const update = bool.fromEnvironment('updateExpectations');
bool semanticEquals = false;
if (goldenFile.existsSync()) {
try {
final goldenContents = await goldenFile.readAsString();
final golden = Recordings.fromJson(jsonDecode(goldenContents));
semanticEquals = actualSemantic.semanticEquals(
golden,
loadingUnitMapping: (unit) {
// dart2wasm and VM assign loading units differently. Work around this
// for now, we'll need a more robust testing solution later.
final fileName = path.basename(sourceFileUri.toFilePath());
if (fileName == 'loading_units_shared_constant.dart') {
if (unit == 'M3') return '3';
}
if (fileName == 'loading_units_nested_shared_constant.dart') {
if (unit == 'M3') return '3';
}
return '${moduleNameToId(unit)! + 1}';
},
);
} on FormatException {
if (!update) {
rethrow;
}
}
}
if (update && !semanticEquals) {
goldenFile.writeAsStringSync(
JsonEncoder.withIndent(' ').convert(actualSemantic.toJson()),
);
print('Updated expectations for $sourceFileUri');
} else if (!semanticEquals) {
final encoder = JsonEncoder.withIndent(' ');
print('Actual:\n${encoder.convert(actualSemantic.toJson())}');
if (goldenFile.existsSync()) {
final goldenContents = await goldenFile.readAsString();
print('Expected:\n$goldenContents');
}
print(
'To update expectations, run: dart -DupdateExpectations=true '
'pkg/dart2wasm/test/record_use_test.dart '
'${path.basename(sourceFileUri.toFilePath())}',
);
throw 'Expectations for $sourceFileUri do not match';
}
});
}
Future<void> main(List<String> args) async {
assert(args.isEmpty || args.length == 1);
final filter = args.firstOrNull;
final recordUseTestDir = _pkgVmDir.resolve(
'testcases/transformations/record_use/',
);
final testCasesDir = Directory.fromUri(recordUseTestDir.resolve('lib/'));
final packagesFileUri = _pkgVmDir.resolve(
'../../.dart_tool/package_config.json',
);
for (var fse in testCasesDir.listSync(recursive: true, followLinks: false)) {
if (fse is! File) continue;
if (fse.path.endsWith('.dart') &&
!fse.path.contains('helper') &&
(filter == null || fse.path.contains(filter))) {
final name = path.basename(fse.path);
if (dart2wasmNotSupported.contains(name)) {
continue;
}
final packageUri = Uri.parse('package:record_use_test/$name');
await runTestCase(fse.uri, packageUri, packagesFileUri);
}
}
await runOutsidePackageThrows(packagesFileUri);
}
Future<void> runOutsidePackageThrows(Uri packagesFileUri) async {
await withTempDir((String tempDir) async {
final sourceFile = File(path.join(tempDir, 'outside.dart'));
sourceFile.writeAsStringSync('''
import 'package:meta/meta.dart' show RecordUse;
class SomeClass {
@RecordUse()
static String someStaticMethod(int a) => a.toString();
}
void main() {
print(SomeClass.someStaticMethod(42));
}
''');
final result = await Process.run(Platform.executable, [
'compile',
'wasm',
'-O2',
'--packages=${packagesFileUri.toFilePath()}',
sourceFile.path,
'-o',
path.join(tempDir, 'out.wasm'),
]);
if (result.exitCode == 0) {
throw 'Compilation succeeded but was expected to fail.';
}
final errors = '${result.stdout}\n${result.stderr}';
if (!errors.contains('RecordUse') || !errors.contains('package:')) {
throw 'Wrong error message for outside package test:\n$errors';
}
});
}