| // 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:io' show File; |
| |
| import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart' |
| show DiagnosticMessage, getMessageCodeObject; |
| |
| import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity; |
| |
| import 'package:expect/expect.dart' show Expect; |
| |
| import 'package:front_end/src/api_prototype/compiler_options.dart' |
| show CompilerOptions; |
| |
| import 'package:front_end/src/api_prototype/experimental_flags.dart' |
| show ExperimentalFlag; |
| |
| import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart' |
| show IncrementalCompilerResult; |
| |
| import "package:front_end/src/api_prototype/memory_file_system.dart" |
| show MemoryFileSystem; |
| |
| import 'package:front_end/src/base/nnbd_mode.dart' show NnbdMode; |
| |
| import 'package:front_end/src/base/processed_options.dart' |
| show ProcessedOptions; |
| |
| import 'package:front_end/src/compute_platform_binaries_location.dart' |
| show computePlatformBinariesLocation; |
| |
| import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; |
| |
| import 'package:front_end/src/fasta/fasta_codes.dart' |
| show |
| Code, |
| codeInitializeFromDillNotSelfContained, |
| codeInitializeFromDillNotSelfContainedNoDump, |
| codeInitializeFromDillUnknownProblem, |
| codeInitializeFromDillUnknownProblemNoDump; |
| |
| import 'package:front_end/src/fasta/incremental_compiler.dart' |
| show IncrementalCompiler, RecorderForTesting; |
| |
| import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent; |
| |
| import 'package:kernel/kernel.dart' |
| show Component, Library, NonNullableByDefaultCompiledMode; |
| |
| import 'incremental_suite.dart' show getOptions; |
| |
| Future<Null> main() async { |
| Tester tester = new Tester(); |
| await tester.initialize(); |
| await tester.test(); |
| } |
| |
| class Tester { |
| late Uri sdkRoot; |
| late Uri base; |
| late Uri sdkSummary; |
| late Uri initializeFrom; |
| late Uri helperFile; |
| late Uri helper2File; |
| late Uri entryPoint; |
| late Uri entryPointImportDartFoo; |
| late Uri platformUri; |
| late List<int> sdkSummaryData; |
| late List<DiagnosticMessage> errorMessages; |
| late List<DiagnosticMessage> warningMessages; |
| late MemoryFileSystem fs; |
| late CompilerOptions options; |
| late IncrementalCompiler compiler; |
| |
| Future<void> compileExpectInitializeFailAndSpecificWarning( |
| Code expectedWarningCode, bool writeFileOnCrashReport) async { |
| errorMessages.clear(); |
| warningMessages.clear(); |
| options.writeFileOnCrashReport = writeFileOnCrashReport; |
| compiler = new DeleteTempFilesIncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [entryPoint])), |
| initializeFrom); |
| await compiler.computeDelta(); |
| if (compiler.initializedFromDillForTesting) { |
| Expect.fail("Expected to not be able to initialized from dill, but did."); |
| } |
| if (errorMessages.isNotEmpty) { |
| Expect.fail("Got unexpected errors: " + joinMessages(errorMessages)); |
| } |
| if (warningMessages.length != 1) { |
| Expect.fail("Got unexpected errors: Expected one, got this: " + |
| joinMessages(warningMessages)); |
| } |
| if (getMessageCodeObject(warningMessages[0]) != expectedWarningCode) { |
| Expect.fail("Expected ${expectedWarningCode.name} but got " + |
| joinMessages(warningMessages)); |
| } |
| } |
| |
| Future<Component> compileExpectOk( |
| bool initializedFromDill, Uri compileThis) async { |
| errorMessages.clear(); |
| warningMessages.clear(); |
| options.writeFileOnCrashReport = false; |
| compiler = new DeleteTempFilesIncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [compileThis])), |
| initializeFrom); |
| IncrementalCompilerResult compilerResult = await compiler.computeDelta(); |
| Component component = compilerResult.component; |
| |
| if (compiler.initializedFromDillForTesting != initializedFromDill) { |
| Expect.fail("Expected initializedFromDill to be $initializedFromDill " |
| "but was ${compiler.initializedFromDillForTesting}"); |
| } |
| if (errorMessages.isNotEmpty) { |
| Expect.fail("Got unexpected errors: " + joinMessages(errorMessages)); |
| } |
| if (warningMessages.isNotEmpty) { |
| Expect.fail("Got unexpected warnings: " + joinMessages(warningMessages)); |
| } |
| |
| return component; |
| } |
| |
| Future<void> initialize() async { |
| sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); |
| base = Uri.parse("org-dartlang-test:///"); |
| sdkSummary = base.resolve("vm_platform.dill"); |
| initializeFrom = base.resolve("initializeFrom.dill"); |
| helperFile = base.resolve("helper.dart"); |
| helper2File = base.resolve("helper2.dart"); |
| entryPoint = base.resolve("small.dart"); |
| entryPointImportDartFoo = base.resolve("small_foo.dart"); |
| platformUri = sdkRoot.resolve("vm_platform_strong.dill"); |
| sdkSummaryData = await new File.fromUri(platformUri).readAsBytes(); |
| errorMessages = <DiagnosticMessage>[]; |
| warningMessages = <DiagnosticMessage>[]; |
| fs = new MemoryFileSystem(base); |
| options = getOptions(); |
| |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummary; |
| options.omitPlatform = true; |
| options.onDiagnostic = (DiagnosticMessage message) { |
| if (message.severity == Severity.error) { |
| errorMessages.add(message); |
| } else if (message.severity == Severity.warning) { |
| warningMessages.add(message); |
| } |
| }; |
| |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); |
| fs.entityForUri(helperFile).writeAsStringSync(""" |
| foo() { |
| print("hello from foo"); |
| } |
| """); |
| fs.entityForUri(helper2File).writeAsStringSync(""" |
| foo2() { |
| print("hello from foo2"); |
| } |
| """); |
| fs.entityForUri(entryPoint).writeAsStringSync(""" |
| import "helper.dart" as helper; |
| main() { |
| helper.foo(); |
| } |
| """); |
| fs.entityForUri(entryPointImportDartFoo).writeAsStringSync(""" |
| import "dart:foo" as helper; |
| main() { |
| helper.foo2(); |
| } |
| """); |
| } |
| |
| Future<Null> test() async { |
| compiler = new IncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [entryPoint])), |
| initializeFrom); |
| |
| IncrementalCompilerResult compilerGoodResult = |
| await compiler.computeDelta(); |
| Component componentGood = compilerGoodResult.component; |
| List<int> dataGood = serializeComponent(componentGood); |
| fs.entityForUri(initializeFrom).writeAsBytesSync(dataGood); |
| |
| // Create fake "dart:foo" library. |
| options.omitPlatform = false; |
| compiler = new IncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [helper2File])), |
| initializeFrom); |
| IncrementalCompilerResult compilerHelperResult = |
| await compiler.computeDelta(); |
| Component componentHelper = compilerHelperResult.component; |
| Library helper2Lib = componentHelper.libraries |
| .firstWhere((lib) => lib.importUri == helper2File); |
| helper2Lib.importUri = new Uri(scheme: "dart", path: "foo"); |
| List<int> sdkWithDartFoo = serializeComponent(componentHelper); |
| options.omitPlatform = true; |
| |
| // Compile with our fake sdk with dart:foo should be ok. |
| List<int> orgSdkBytes = await fs.entityForUri(sdkSummary).readAsBytes(); |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkWithDartFoo); |
| Component component = await compileExpectOk(true, entryPointImportDartFoo); |
| fs.entityForUri(sdkSummary).writeAsBytesSync(orgSdkBytes); |
| if (component.libraries.length != 1) { |
| Expect.fail("Expected 1 library, got ${component.libraries.length}: " |
| "${component.libraries}"); |
| } |
| List<int> dataLinkedToSdkWithFoo = serializeComponent(component); |
| |
| // Initialize from good dill file should be ok. |
| await compileExpectOk(true, entryPoint); |
| |
| // Create a partial dill file. |
| compiler.invalidate(entryPoint); |
| IncrementalCompilerResult compilerResult = await compiler.computeDelta(); |
| component = compilerResult.component; |
| if (component.libraries.length != 1) { |
| Expect.fail("Expected 1 library, got ${component.libraries.length}: " |
| "${component.libraries}"); |
| } |
| List<int> data = serializeComponent(component); |
| fs.entityForUri(initializeFrom).writeAsBytesSync(data); |
| |
| // Initializing from partial dill should not be ok. |
| await compileExpectInitializeFailAndSpecificWarning( |
| codeInitializeFromDillNotSelfContained, true); |
| await compileExpectInitializeFailAndSpecificWarning( |
| codeInitializeFromDillNotSelfContainedNoDump, false); |
| |
| // Create a invalid dill file to load from: Should not be ok. |
| data = new List<int>.filled(42, 42); |
| fs.entityForUri(initializeFrom).writeAsBytesSync(data); |
| await compileExpectInitializeFailAndSpecificWarning( |
| codeInitializeFromDillUnknownProblem, true); |
| await compileExpectInitializeFailAndSpecificWarning( |
| codeInitializeFromDillUnknownProblemNoDump, false); |
| |
| // Create a dill with a reference to a non-existing sdk thing: |
| // Should be ok (for now), but we shouldn't actually initialize from dill. |
| fs.entityForUri(initializeFrom).writeAsBytesSync(dataLinkedToSdkWithFoo); |
| await compileExpectOk(false, entryPoint); |
| |
| // Try to initialize from a dill which contains mixed compilation modes: |
| // Should be ok, but we shouldn't actually initialize from dill. |
| List<int> mixedPart1; |
| { |
| // Create a component that is compiled without NNBD. |
| Map<ExperimentalFlag, bool>? prevTesting = |
| options.defaultExperimentFlagsForTesting; |
| options.defaultExperimentFlagsForTesting = { |
| ExperimentalFlag.nonNullable: false |
| }; |
| NnbdMode prevNnbd = options.nnbdMode; |
| options.nnbdMode = NnbdMode.Weak; |
| compiler = new IncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [helper2File])), |
| null); |
| |
| IncrementalCompilerResult result = await compiler.computeDelta(); |
| Component c = result.component; |
| c.setMainMethodAndMode( |
| null, false, NonNullableByDefaultCompiledMode.Weak); |
| mixedPart1 = serializeComponent(c); |
| options.defaultExperimentFlagsForTesting = prevTesting; |
| options.nnbdMode = prevNnbd; |
| } |
| |
| List<int> mixedPart2; |
| { |
| // Create a component that is compiled with strong NNBD. |
| Map<ExperimentalFlag, bool>? prevTesting = |
| options.defaultExperimentFlagsForTesting; |
| options.defaultExperimentFlagsForTesting = { |
| ExperimentalFlag.nonNullable: true |
| }; |
| NnbdMode prevNnbd = options.nnbdMode; |
| options.nnbdMode = NnbdMode.Strong; |
| compiler = new IncrementalCompiler( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [helperFile])), |
| null); |
| IncrementalCompilerResult result = await compiler.computeDelta(); |
| Component c = result.component; |
| c.setMainMethodAndMode( |
| null, false, NonNullableByDefaultCompiledMode.Strong); |
| mixedPart2 = serializeComponent(c); |
| options.defaultExperimentFlagsForTesting = prevTesting; |
| options.nnbdMode = prevNnbd; |
| } |
| |
| // Now mix the two components together and try to initialize from them. |
| // We expect the compilation to be OK but that the dill is not actually |
| // used to initialize from (as it's invalid because of the mixed mode). |
| List<int> mixed = []; |
| mixed.addAll(mixedPart1); |
| mixed.addAll(mixedPart2); |
| fs.entityForUri(initializeFrom).writeAsBytesSync(mixed); |
| await compileExpectOk(false, entryPoint); |
| } |
| } |
| |
| class DeleteTempFilesIncrementalCompiler extends IncrementalCompiler { |
| DeleteTempFilesIncrementalCompiler(CompilerContext context, |
| [Uri? initializeFromDillUri]) |
| : super(context, initializeFromDillUri); |
| |
| @override |
| final RecorderForTesting recorderForTesting = |
| const DeleteTempFilesRecorderForTesting(); |
| } |
| |
| class DeleteTempFilesRecorderForTesting extends RecorderForTesting { |
| const DeleteTempFilesRecorderForTesting(); |
| |
| @override |
| void recordTemporaryFile(Uri uri) { |
| File f = new File.fromUri(uri); |
| if (f.existsSync()) f.deleteSync(); |
| } |
| } |
| |
| String joinMessages(List<DiagnosticMessage> messages) { |
| return messages.map((m) => m.plainTextFormatted.join("\n")).join("\n"); |
| } |