| // Copyright (c) 2018, 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:async'; | |
| import 'dart:io'; | |
| import 'package:dart2wasm/target.dart' show WasmTarget; | |
| import 'package:front_end/src/api_unstable/vm.dart' | |
| show | |
| CompilerOptions, | |
| CfeDiagnosticMessage, | |
| computePlatformBinariesLocation, | |
| kernelForModule, | |
| kernelForProgram, | |
| parseExperimentalArguments, | |
| parseExperimentalFlags; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/text/ast_to_text.dart' show Printer; | |
| import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter; | |
| import 'package:kernel/target/targets.dart'; | |
| import 'package:test/test.dart'; | |
| /// Environment define to update expectation files on failures. | |
| const kUpdateExpectations = 'updateExpectations'; | |
| /// Environment define to dump actual results alongside expectations. | |
| const kDumpActualResult = 'dump.actual.result'; | |
| Future<Component> compileTestCaseToKernelProgram( | |
| Uri sourceUri, { | |
| required Target target, | |
| List<String>? experimentalFlags, | |
| Map<String, String>? environmentDefines, | |
| Uri? packagesFileUri, | |
| List<Uri>? linkedDependencies, | |
| }) async { | |
| Directory? tempDirectory; | |
| try { | |
| final platformFileName = (target is WasmTarget) | |
| ? target.platformFile | |
| : 'vm_platform.dill'; | |
| final platformKernel = computePlatformBinariesLocation().resolve( | |
| platformFileName, | |
| ); | |
| environmentDefines ??= <String, String>{}; | |
| final options = new CompilerOptions() | |
| ..target = target | |
| ..sdkSummary = platformKernel | |
| ..environmentDefines = environmentDefines | |
| ..packagesFileUri = packagesFileUri | |
| ..explicitExperimentalFlags = parseExperimentalFlags( | |
| parseExperimentalArguments(experimentalFlags), | |
| onError: (String message) { | |
| throw message; | |
| }, | |
| ) | |
| ..onDiagnostic = (CfeDiagnosticMessage message) { | |
| fail("Compilation error: ${message.plainTextFormatted.join('\n')}"); | |
| }; | |
| if (linkedDependencies != null) { | |
| final Component component = (await kernelForModule( | |
| linkedDependencies, | |
| options, | |
| )).component!; | |
| tempDirectory = await Directory.systemTemp.createTemp(); | |
| Uri uri = tempDirectory.uri.resolve("generated.dill"); | |
| File generated = new File.fromUri(uri); | |
| IOSink sink = generated.openWrite(); | |
| try { | |
| new BinaryPrinter(sink).writeComponentFile(component); | |
| } finally { | |
| await sink.close(); | |
| } | |
| options..additionalDillModules = <Uri>[uri]; | |
| } | |
| 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; | |
| } finally { | |
| await tempDirectory?.delete(recursive: true); | |
| } | |
| } | |
| /// Returns a human-readable string representation of [library]. | |
| /// | |
| /// If [removeSelectorIds] is provided, selector ids above 99 are removed. | |
| /// Extra libraries apart from the main library are passed to the front-end as | |
| /// additional dills, which places them last in the library list, causing them | |
| /// to have very high (and often changing) selector IDs. | |
| String kernelLibraryToString( | |
| Library library, { | |
| bool removeSelectorIds = false, | |
| }) { | |
| final StringBuffer buffer = new StringBuffer(); | |
| final printer = new Printer(buffer, showMetadata: true); | |
| printer.writeLibraryFile(library); | |
| printer.writeConstantTable(library.enclosingComponent!); | |
| String result = buffer.toString(); | |
| final libraryName = library.name; | |
| if (libraryName != null) { | |
| result = result.replaceAll(library.importUri.toString(), library.name!); | |
| } | |
| if (removeSelectorIds) { | |
| result = result | |
| .replaceAll(RegExp(r',methodOrSetterSelectorId:\d{3,}'), '') | |
| .replaceAll(RegExp(r',getterSelectorId:\d{3,}'), ''); | |
| } | |
| return result; | |
| } | |
| String kernelComponentToString(Component component) { | |
| final StringBuffer buffer = new StringBuffer(); | |
| new Printer(buffer, showMetadata: true).writeComponentFile(component); | |
| final mainLibrary = component.mainMethod!.enclosingLibrary; | |
| return buffer.toString().replaceAll( | |
| mainLibrary.importUri.toString(), | |
| mainLibrary.name!, | |
| ); | |
| } | |
| class DevNullSink<T> implements Sink<T> { | |
| @override | |
| void add(T data) {} | |
| @override | |
| void close() {} | |
| } | |
| void ensureKernelCanBeSerializedToBinary(Component component) { | |
| new BinaryPrinter(new DevNullSink<List<int>>()).writeComponentFile(component); | |
| } | |
| 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 new Difference(i + 1, actualLines[i], expectedLines[i]); | |
| } | |
| } | |
| return new 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 { | |
| if (bool.fromEnvironment(kDumpActualResult)) { | |
| new File('$baseFilename.actual').writeAsStringSync(actual); | |
| } | |
| 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 --timeout 600 pkg/vm/ | |
| In order to dump actual results into .actual files run tests with -D$kDumpActualResult=true VM option: | |
| tools/test.py -m release --vm-options -D$kDumpActualResult=true --timeout 600 pkg/vm/ | |
| """); | |
| } | |
| } | |
| } |