|  | // Copyright (c) 2024, 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:io'; | 
|  | import 'dart:typed_data' show BytesBuilder; | 
|  |  | 
|  | import 'package:dart2bytecode/bytecode_generator.dart' show generateBytecode; | 
|  | import 'package:dart2bytecode/bytecode_serialization.dart' | 
|  | show LinkReader, BufferedReader; | 
|  | import 'package:dart2bytecode/declarations.dart' as bytecode_declarations | 
|  | show Component; | 
|  | import 'package:dart2bytecode/options.dart' show BytecodeOptions; | 
|  | import 'package:front_end/src/api_unstable/vm.dart' | 
|  | show | 
|  | CompilerOptions, | 
|  | computePlatformBinariesLocation, | 
|  | DiagnosticMessage, | 
|  | kernelForProgram; | 
|  | import 'package:kernel/ast.dart'; | 
|  | import 'package:kernel/core_types.dart'; | 
|  | import 'package:kernel/class_hierarchy.dart'; | 
|  | import 'package:kernel/kernel.dart'; | 
|  | import 'package:kernel/target/targets.dart'; | 
|  | import 'package:test/test.dart'; | 
|  | import 'package:vm/modular/target/vm.dart'; | 
|  |  | 
|  | /// Environment define to update expectation files on failures. | 
|  | const kUpdateExpectations = 'updateExpectations'; | 
|  |  | 
|  | final String dartSdkPkgDir = Platform.script.resolve('../..').toFilePath(); | 
|  |  | 
|  | runTestCase(Uri source) async { | 
|  | final target = VmTarget(TargetFlags()); | 
|  | Component component = | 
|  | await compileTestCaseToKernelProgram(source, target: target); | 
|  |  | 
|  | final mainLibrary = component.mainMethod!.enclosingLibrary; | 
|  | final coreTypes = CoreTypes(component); | 
|  | final hierarchy = ClassHierarchy(component, coreTypes); | 
|  |  | 
|  | final sink = ByteSink(); | 
|  | generateBytecode(component, sink, | 
|  | options: BytecodeOptions(), | 
|  | libraries: [mainLibrary], | 
|  | coreTypes: coreTypes, | 
|  | hierarchy: hierarchy, | 
|  | target: target); | 
|  |  | 
|  | final reader = BufferedReader(LinkReader(), sink.builder.takeBytes()); | 
|  | String actual = bytecode_declarations.Component.read(reader).toString(); | 
|  |  | 
|  | // Remove absolute library URIs. | 
|  | actual = actual.replaceAll( | 
|  | new Uri.file(dartSdkPkgDir).toString(), 'DART_SDK/pkg/'); | 
|  |  | 
|  | compareResultWithExpectationsFile(source, actual); | 
|  | } | 
|  |  | 
|  | Future<Component> compileTestCaseToKernelProgram(Uri sourceUri, | 
|  | {required Target target}) async { | 
|  | final platformKernel = | 
|  | computePlatformBinariesLocation().resolve('vm_platform_strong.dill'); | 
|  | final options = CompilerOptions() | 
|  | ..target = target | 
|  | ..additionalDills = <Uri>[platformKernel] | 
|  | ..environmentDefines = {} | 
|  | ..onDiagnostic = (DiagnosticMessage message) { | 
|  | fail("Compilation error: ${message.plainTextFormatted.join('\n')}"); | 
|  | }; | 
|  |  | 
|  | final Component component = | 
|  | (await kernelForProgram(sourceUri, options))!.component!; | 
|  |  | 
|  | // Make sure the library name is the same and does not depend on the order | 
|  | // of test cases. | 
|  | component.mainMethod!.enclosingLibrary.name = '#lib'; | 
|  | return component; | 
|  | } | 
|  |  | 
|  | class ByteSink implements Sink<List<int>> { | 
|  | final BytesBuilder builder = BytesBuilder(); | 
|  |  | 
|  | @override | 
|  | void add(List<int> data) { | 
|  | builder.add(data); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void close() {} | 
|  | } | 
|  |  | 
|  | class Difference { | 
|  | final int line; | 
|  | final String actual; | 
|  | final String expected; | 
|  |  | 
|  | Difference(this.line, this.actual, this.expected); | 
|  | } | 
|  |  | 
|  | Difference findFirstDifference(String actual, String expected) { | 
|  | final actualLines = actual.split('\n'); | 
|  | final expectedLines = expected.split('\n'); | 
|  | int i = 0; | 
|  | for (; i < actualLines.length && i < expectedLines.length; ++i) { | 
|  | if (actualLines[i] != expectedLines[i]) { | 
|  | return Difference(i + 1, actualLines[i], expectedLines[i]); | 
|  | } | 
|  | } | 
|  | return Difference(i + 1, i < actualLines.length ? actualLines[i] : '<END>', | 
|  | i < expectedLines.length ? expectedLines[i] : '<END>'); | 
|  | } | 
|  |  | 
|  | void compareResultWithExpectationsFile( | 
|  | Uri source, | 
|  | String actual, { | 
|  | String expectFilePostfix = '', | 
|  | }) { | 
|  | final baseFilename = '${source.toFilePath()}$expectFilePostfix'; | 
|  | final expectFile = new File('$baseFilename.expect'); | 
|  | final expected = expectFile.existsSync() ? expectFile.readAsStringSync() : ''; | 
|  |  | 
|  | if (actual != expected) { | 
|  | if (bool.fromEnvironment(kUpdateExpectations)) { | 
|  | expectFile.writeAsStringSync(actual); | 
|  | print("  Updated $expectFile"); | 
|  | } else { | 
|  | Difference diff = findFirstDifference(actual, expected); | 
|  | fail(""" | 
|  |  | 
|  | Result is different for the test case $source | 
|  |  | 
|  | The first difference is at line ${diff.line}. | 
|  | Actual:   ${diff.actual} | 
|  | Expected: ${diff.expected} | 
|  |  | 
|  | This failure can be caused by changes in the front-end if it starts generating | 
|  | different kernel AST for the same Dart programs. | 
|  |  | 
|  | In order to re-generate expectations run tests with -D$kUpdateExpectations=true VM option: | 
|  |  | 
|  | tools/test.py -m release --vm-options -D$kUpdateExpectations=true pkg/dart2bytecode/ | 
|  |  | 
|  | """); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | main() { | 
|  | group('gen-bytecode', () { | 
|  | final testCasesDir = | 
|  | new Directory(dartSdkPkgDir + 'dart2bytecode/testcases'); | 
|  |  | 
|  | for (var entry | 
|  | in testCasesDir.listSync(recursive: true, followLinks: false)) { | 
|  | if (entry.path.endsWith(".dart")) { | 
|  | test(entry.path, () => runTestCase(entry.uri)); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } |