Index: tests/fletchc/incremental/feature_test.dart |
diff --git a/tests/fletchc/incremental/feature_test.dart b/tests/fletchc/incremental/feature_test.dart |
deleted file mode 100644 |
index 080b3b35799effd30e17bd0118bf333bba24ad67..0000000000000000000000000000000000000000 |
--- a/tests/fletchc/incremental/feature_test.dart |
+++ /dev/null |
@@ -1,713 +0,0 @@ |
-// Copyright (c) 2014, 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. |
- |
-library fletchc.test.feature_test; |
- |
-import 'dart:io' hide |
- exitCode, |
- stderr, |
- stdin, |
- stdout; |
- |
-import 'dart:io' as io; |
- |
-import 'dart:async' show |
- Completer, |
- Future, |
- Stream, |
- StreamController, |
- StreamIterator; |
- |
-import 'dart:convert' show |
- LineSplitter, |
- UTF8, |
- Utf8Decoder; |
- |
-import 'package:expect/expect.dart' show |
- Expect; |
- |
-import 'package:fletchc/incremental/scope_information_visitor.dart' show |
- ScopeInformationVisitor; |
- |
-import 'compiler_test_case.dart' show |
- CompilerTestCase; |
- |
-import 'package:compiler/src/elements/elements.dart' show |
- AbstractFieldElement, |
- Element, |
- FieldElement, |
- FunctionElement, |
- LibraryElement; |
- |
-import 'package:compiler/src/compiler.dart' show |
- Compiler; |
- |
-import 'package:compiler/src/source_file_provider.dart' show |
- FormattingDiagnosticHandler, |
- SourceFileProvider; |
- |
-import 'package:compiler/src/io/source_file.dart' show |
- StringSourceFile; |
- |
-import 'package:fletchc/incremental/fletchc_incremental.dart' show |
- IncrementalCompilationFailed, |
- IncrementalCompiler, |
- IncrementalMode; |
- |
-import 'package:fletchc/vm_commands.dart' show |
- VmCommand, |
- CommitChanges, |
- CommitChangesResult, |
- HandShakeResult, |
- MapId; |
- |
-import 'package:fletchc/fletch_compiler.dart' show |
- FletchCompiler; |
- |
-import 'package:fletchc/src/fletch_compiler_implementation.dart' show |
- OutputProvider; |
- |
-import 'package:fletchc/src/guess_configuration.dart' show |
- fletchVersion, |
- guessFletchVm; |
- |
-import 'package:fletchc/fletch_system.dart'; |
- |
-import 'package:fletchc/vm_commands.dart' as commands_lib; |
- |
-import 'package:fletchc/vm_session.dart' show |
- Session; |
- |
-import 'package:fletchc/src/fletch_backend.dart' show |
- FletchBackend; |
- |
-import 'package:fletchc/fletch_vm.dart' show |
- FletchVm; |
- |
-import 'package:fletchc/debug_state.dart' as debug show |
- BackTraceFrame, |
- BackTrace; |
- |
-import 'program_result.dart'; |
- |
-import 'package:fletchc/src/hub/exit_codes.dart' show |
- DART_VM_EXITCODE_COMPILE_TIME_ERROR; |
- |
-const String PACKAGE_SCHEME = 'org.dartlang.fletch.packages'; |
- |
-const String CUSTOM_SCHEME = 'org.dartlang.fletch.test-case'; |
- |
-final Uri customUriBase = new Uri(scheme: CUSTOM_SCHEME, path: '/'); |
- |
-typedef Future NoArgFuture(); |
- |
-compileAndRun( |
- String testName, |
- EncodedResult encodedResult, |
- {IncrementalMode incrementalMode}) async { |
- print("Test '$testName'"); |
- IncrementalTestHelper helper = new IncrementalTestHelper(incrementalMode); |
- TestSession session = |
- await TestSession.spawnVm(helper.compiler, testName: testName); |
- |
- bool hasCompileTimeError = false; |
- |
- await new Future(() async { |
- |
- int version = 0; |
- |
- for (ProgramResult program in encodedResult.decode()) { |
- bool isFirstProgram = version == 0; |
- version++; |
- |
- if (!program.compileUpdatesShouldThrow) { |
- // We should only update the status of hasCompileTimeError when we run |
- // something on the VM. |
- hasCompileTimeError = program.hasCompileTimeError; |
- } |
- |
- print("Program version $version #$testName:"); |
- print(numberedLines(program.code)); |
- |
- bool compileUpdatesThrew = true; |
- FletchDelta fletchDelta; |
- if (isFirstProgram) { |
- // The first program is compiled "fully". |
- fletchDelta = await helper.fullCompile(program); |
- compileUpdatesThrew = false; |
- } else { |
- // An update to the first program, all updates are compiled as |
- // incremental updates to the first program. |
- try { |
- fletchDelta = await helper.incrementalCompile(program, version); |
- compileUpdatesThrew = false; |
- } on IncrementalCompilationFailed catch (error) { |
- if (program.compileUpdatesShouldThrow) { |
- print("Expected error in compileUpdates: $error"); |
- } else { |
- print("Unexpected error in compileUpdates."); |
- rethrow; |
- } |
- } |
- } |
- |
- FletchBackend backend = helper.compiler.compiler.context.backend; |
- |
- if (program.compileUpdatesShouldThrow) { |
- Expect.isFalse(isFirstProgram); |
- Expect.isTrue( |
- compileUpdatesThrew, |
- "Expected an exception in compileUpdates"); |
- Expect.isNull(fletchDelta, "Expected update == null"); |
- break; |
- } |
- |
- if (!isFirstProgram || |
- const bool.fromEnvironment("feature_test.print_initial_commands")) { |
- for (VmCommand command in fletchDelta.commands) { |
- print(command); |
- } |
- } |
- |
- if (isFirstProgram) { |
- // Perform handshake with VM. |
- HandShakeResult handShakeResult = |
- await session.handShake(fletchVersion); |
- Expect.isTrue(handShakeResult.success, "Fletch VM version mismatch"); |
- } |
- |
- CommitChangesResult result = await session.applyDelta(fletchDelta); |
- |
- if (!result.successful) { |
- print("The CommitChanges() command was not successful: " |
- "${result.message}"); |
- } |
- |
- Expect.equals(result.successful, !program.commitChangesShouldFail, |
- result.message); |
- |
- if (isFirstProgram) { |
- // Turn on debugging. |
- await session.enableDebugger(); |
- // Spawn the process to run. |
- await session.spawnProcess(); |
- // Allow operations on internal frames. |
- await session.toggleInternal(); |
- } |
- |
- if (result.successful) { |
- // Set breakpoint in main in case main was replaced. |
- var breakpoints = |
- await session.setBreakpoint(methodName: "main", bytecodeIndex: 0); |
- for (var breakpoint in breakpoints) { |
- print("Added breakpoint: $breakpoint"); |
- } |
- if (!helper.compiler.compiler.mainFunction.isMalformed) { |
- // If there's a syntax error in main, we cannot find it to set a |
- // breakpoint. |
- // TODO(ahe): Consider if this is a problem? |
- Expect.equals(1, breakpoints.length); |
- } |
- if (isFirstProgram) { |
- // Run the program to hit the breakpoint in main. |
- await session.debugRun(); |
- } else { |
- // Restart the current frame to rerun main. |
- await session.restart(); |
- } |
- if (session.running) { |
- // Step out of main to finish execution of main. |
- await session.stepOut(); |
- } |
- |
- // Select the stack frame of callMain. |
- debug.BackTrace trace = await session.backTrace(); |
- FunctionElement callMainElement = |
- backend.fletchSystemLibrary.findLocal("callMain"); |
- FletchFunction callMain = |
- helper.system.lookupFunctionByElement(callMainElement); |
- debug.BackTraceFrame mainFrame = |
- trace.frames.firstWhere( |
- (debug.BackTraceFrame frame) => frame.function == callMain); |
- int frame = trace.frames.indexOf(mainFrame); |
- Expect.notEquals(1, frame); |
- session.selectFrame(frame); |
- print(trace.format()); |
- |
- List<String> actualMessages = session.stdoutSink.takeLines(); |
- |
- List<String> messages = new List<String>.from(program.messages); |
- if (program.hasCompileTimeError) { |
- print("Compile-time error expected"); |
- // TODO(ahe): The compile-time error message shouldn't be printed by |
- // the Fletch VM. |
- |
- // Find the compile-time error message in the actual output, and |
- // remove all lines after it. |
- int compileTimeErrorIndex = -1; |
- for (int i = 0; i < actualMessages.length; i++) { |
- if (actualMessages[i].startsWith("Compile error:")) { |
- compileTimeErrorIndex = i; |
- break; |
- } |
- } |
- Expect.isTrue(compileTimeErrorIndex != -1); |
- actualMessages.removeRange(compileTimeErrorIndex, |
- actualMessages.length); |
- } |
- |
- Expect.listEquals(messages, actualMessages, |
- "Expected $messages, got $actualMessages"); |
- |
- // TODO(ahe): Enable SerializeScopeTestCase for multiple parts. |
- if (!isFirstProgram && program.code is String) { |
- await new SerializeScopeTestCase( |
- program.code, helper.compiler.mainApp, |
- helper.compiler.compiler).run(); |
- } |
- } |
- } |
- |
- // If everything went fine, we will try finishing the execution and do a |
- // graceful shutdown. |
- if (session.running) { |
- // The session is still alive. Run to completion. |
- var continueCommand = const commands_lib.ProcessContinue(); |
- print(continueCommand); |
- |
- // Wait for process termination. |
- VmCommand response = await session.runCommand(continueCommand); |
- if (response is! commands_lib.ProcessTerminated) { |
- // TODO(ahe): It's probably an instance of |
- // commands_lib.UncaughtException, and if so, we should try to print |
- // the stack trace. |
- throw new StateError( |
- "Expected ProcessTerminated, but got: $response"); |
- } |
- } |
- await session.runCommand(const commands_lib.SessionEnd()); |
- await session.shutdown(); |
- }).catchError(session.handleError); |
- |
- // TODO(ahe/kustermann/ager): We really need to distinguish VM crashes from |
- // normal test failures. This information is based on exitCode and we need |
- // to propagate the exitCode back to test.dart, so we can have Fail/Crash |
- // outcomes of these tests. |
- await session.waitForCompletion(); |
- |
- int actualExitCode = await session.exitCode; |
- // TODO(ahe): We should expect exit code 0, and instead be able to detect |
- // compile-time errors directly via the session. |
- int expectedExitCode = hasCompileTimeError |
- ? DART_VM_EXITCODE_COMPILE_TIME_ERROR : 0; |
- Expect.equals( |
- expectedExitCode, actualExitCode, "Unexpected exit code from fletch VM"); |
-} |
- |
-class SerializeScopeTestCase extends CompilerTestCase { |
- final String source; |
- |
- final String scopeInfo; |
- |
- final Compiler compiler = null; // TODO(ahe): Provide a compiler. |
- |
- SerializeScopeTestCase( |
- this.source, |
- LibraryElement library, |
- Compiler compiler) |
- : scopeInfo = computeScopeInfo(compiler, library), |
- super(library.canonicalUri); |
- |
- Future run() { |
- if (true) { |
- // TODO(ahe): Remove this. We're temporarily bypassing scope validation. |
- return new Future.value(null); |
- } |
- return loadMainApp().then(checkScopes); |
- } |
- |
- void checkScopes(LibraryElement library) { |
- var compiler = null; |
- Expect.stringEquals(computeScopeInfo(compiler, library), scopeInfo); |
- } |
- |
- Future<LibraryElement> loadMainApp() async { |
- LibraryElement library = |
- await compiler.libraryLoader.loadLibrary(scriptUri); |
- if (compiler.mainApp == null) { |
- compiler.mainApp = library; |
- } else if (compiler.mainApp != library) { |
- throw "Inconsistent use of compiler (${compiler.mainApp} != $library)."; |
- } |
- return library; |
- } |
- |
- static String computeScopeInfo(Compiler compiler, LibraryElement library) { |
- ScopeInformationVisitor visitor = |
- new ScopeInformationVisitor(compiler, library, 0); |
- |
- visitor.ignoreImports = true; |
- visitor.sortMembers = true; |
- visitor.indented.write('[\n'); |
- visitor.indentationLevel++; |
- visitor.indented; |
- library.accept(visitor, null); |
- library.forEachLocalMember((Element member) { |
- if (member.isClass) { |
- visitor.buffer.write(',\n'); |
- visitor.indented; |
- member.accept(visitor, null); |
- } |
- }); |
- visitor.buffer.write('\n'); |
- visitor.indentationLevel--; |
- visitor.indented.write(']'); |
- return '${visitor.buffer}'; |
- } |
-} |
- |
-void logger(x) { |
- print(x); |
-} |
- |
-String numberedLines(code) { |
- if (code is! Map) { |
- code = {'main.dart': code}; |
- } |
- StringBuffer result = new StringBuffer(); |
- code.forEach((String fileName, String code) { |
- result.writeln("==> $fileName <=="); |
- int lineNumber = 1; |
- for (String text in splitLines(code)) { |
- result.write("$lineNumber: $text"); |
- lineNumber++; |
- } |
- }); |
- return '$result'; |
-} |
- |
-List<String> splitLines(String text) { |
- return text.split(new RegExp('^', multiLine: true)); |
-} |
- |
-class TestSession extends Session { |
- final Process process; |
- final StreamIterator stdoutIterator; |
- final Stream<String> stderr; |
- |
- final List<Future> futures; |
- |
- final Future<int> exitCode; |
- |
- bool isWaitingForCompletion = false; |
- |
- TestSession( |
- Socket vmSocket, |
- IncrementalCompiler compiler, |
- this.process, |
- this.stdoutIterator, |
- this.stderr, |
- this.futures, |
- this.exitCode) |
- : super(vmSocket, compiler, new BytesSink(), null); |
- |
- // Refines type of [stdoutSink]. |
- BytesSink get stdoutSink => super.stdoutSink; |
- |
- void writeStdout(String s) { |
- // Unfortunately, print will always add a newline, and the alternative is |
- // to use stdout.write. However, to make it easier to debug problems in |
- // this and other fletch_tests, everything that is printed to stdout ends |
- // up on the console of test.dart. This is good enough for testing, but DO |
- // NOT COPY TO PRODUCTION CODE. |
- print(s); |
- } |
- |
- void writeStdoutLine(String s) { |
- print(s); |
- } |
- |
- /// Add [future] to this session. All futures that can fail after calling |
- /// [waitForCompletion] must be added to the session. |
- void recordFuture(Future future) { |
- futures.add(convertErrorToString(future)); |
- } |
- |
- void addError(error, StackTrace stackTrace) { |
- recordFuture(new Future.error(error, stackTrace)); |
- } |
- |
- /// Waits for the VM to shutdown and any futures added with [add] to |
- /// complete, and report all errors that occurred. |
- Future waitForCompletion() async { |
- if (isWaitingForCompletion) { |
- throw "waitForCompletion called more than once."; |
- } |
- isWaitingForCompletion = true; |
- // [stderr] and [iterator] (stdout) must have active listeners before |
- // waiting for [futures] below to avoid a deadlock. |
- Future<List<String>> stderrFuture = stderr.toList(); |
- Future<List<String>> stdoutFuture = (() async { |
- List<String> result = <String>[]; |
- while (await stdoutIterator.moveNext()) { |
- result.add(stdoutIterator.current); |
- } |
- return result; |
- })(); |
- |
- StringBuffer sb = new StringBuffer(); |
- int problemCount = 0; |
- for (var error in await Future.wait(futures)) { |
- if (error != null) { |
- sb.writeln("Problem #${++problemCount}:"); |
- sb.writeln(error); |
- sb.writeln(""); |
- } |
- } |
- await stdoutFuture; |
- List<String> stdoutLines = stdoutSink.takeLines(); |
- List<String> stderrLines = await stderrFuture; |
- if (stdoutLines.isNotEmpty) { |
- sb.writeln("Problem #${++problemCount}:"); |
- sb.writeln("Unexpected stdout from fletch-vm:"); |
- for (String line in stdoutLines) { |
- sb.writeln(line); |
- } |
- sb.writeln(""); |
- } |
- if (stderrLines.isNotEmpty) { |
- sb.writeln("Problem #${++problemCount}:"); |
- sb.writeln("Unexpected stderr from fletch-vm:"); |
- for (String line in stderrLines) { |
- sb.writeln(line); |
- } |
- sb.writeln(""); |
- } |
- if (problemCount > 0) { |
- throw new StateError('Test has $problemCount problem(s). Details:\n$sb'); |
- } |
- } |
- |
- static Future<String> convertErrorToString(Future future) { |
- return future.then((_) => null).catchError((error, stackTrace) { |
- return "$error\n$stackTrace"; |
- }); |
- } |
- |
- static Future<TestSession> spawnVm( |
- IncrementalCompiler compiler, |
- {String testName}) async { |
- String vmPath = guessFletchVm(null).toFilePath(); |
- |
- List<Future> futures = <Future>[]; |
- void recordFuture(String name, Future future) { |
- if (future != null) { |
- futures.add(convertErrorToString(future)); |
- } |
- } |
- |
- List<String> vmOptions = <String>[ |
- '-Xvalidate-heaps', |
- ]; |
- |
- print("Running '$vmPath ${vmOptions.join(" ")}'"); |
- var environment = getProcessEnvironment(testName); |
- FletchVm fletchVm = await FletchVm.start( |
- vmPath, arguments: vmOptions, environment: environment); |
- |
- // Unlike [fletchvm.stdoutLines] and [fletchvm.stderrLines], their |
- // corresponding controller cannot produce an error. |
- StreamController<String> stdoutController = new StreamController<String>(); |
- StreamController<String> stderrController = new StreamController<String>(); |
- recordFuture("stdout", fletchVm.stdoutLines.listen((String line) { |
- print('fletch_vm_stdout: $line'); |
- stdoutController.add(line); |
- }).asFuture().whenComplete(stdoutController.close)); |
- recordFuture("stderr", fletchVm.stderrLines.listen((String line) { |
- print('fletch_vm_stderr: $line'); |
- stderrController.add(line); |
- }).asFuture().whenComplete(stderrController.close)); |
- |
- Completer<int> exitCodeCompleter = new Completer<int>(); |
- |
- // TODO(ahe): If the VM crashes on startup, this will never complete. This |
- // makes this program hang forever. But the exitCode completer might |
- // actually be ready to give us a crashed exit code. Exiting early with a |
- // failure in case exitCode is ready before server.first or having a |
- // timeout on server.first would be possible solutions. |
- var vmSocket = await fletchVm.connect(); |
- recordFuture("vmSocket", vmSocket.done); |
- |
- TestSession session = new TestSession( |
- vmSocket, compiler, fletchVm.process, |
- new StreamIterator(stdoutController.stream), |
- stderrController.stream, |
- futures, exitCodeCompleter.future); |
- |
- recordFuture("exitCode", fletchVm.exitCode.then((int exitCode) { |
- print("VM exited with exit code: $exitCode."); |
- exitCodeCompleter.complete(exitCode); |
- })); |
- |
- return session; |
- } |
- |
- static Map<String, String> getProcessEnvironment(String testName) { |
- if (testName == null) return null; |
- |
- var environment = new Map.from(Platform.environment); |
- environment['FEATURE_TEST_TESTNAME'] = testName; |
- return environment; |
- } |
- |
- Future handleError(error, StackTrace stackTrace) { |
- addError(error, stackTrace); |
- |
- // We either failed before we got to start a process or there was an |
- // uncaught exception in the program. If there was an uncaught exception |
- // the VM is intentionally hanging to give the debugger a chance to inspect |
- // the state at the point of the throw. Therefore, we explicitly have to |
- // kill the VM process. Notice, it is important that we kill the VM before |
- // we close the socket to it. Otherwise, the VM may write a message on |
- // stderr claiming that the compiler died (due to the socket getting |
- // closed). |
- process.kill(); |
- |
- // After the process has been killed, we need to close the socket and |
- // discard any commands that may have arrived. |
- recordFuture(process.exitCode.then((_) => kill())); |
- |
- return waitForCompletion(); |
- } |
-} |
- |
-class BytesSink implements Sink<List<int>> { |
- final BytesBuilder builder = new BytesBuilder(); |
- |
- void add(List<int> data) => builder.add(data); |
- |
- void close() { |
- } |
- |
- List<String> takeLines() { |
- return new LineSplitter().convert(UTF8.decode(builder.takeBytes())); |
- } |
-} |
- |
-class IncrementalTestHelper { |
- final Uri packageConfig; |
- |
- final IoInputProvider inputProvider; |
- |
- final IncrementalCompiler compiler; |
- |
- FletchSystem system; |
- |
- IncrementalTestHelper.internal( |
- this.packageConfig, |
- this.inputProvider, |
- this.compiler); |
- |
- factory IncrementalTestHelper(IncrementalMode incrementalMode) { |
- Uri packageConfig = Uri.base.resolve('.packages'); |
- IoInputProvider inputProvider = new IoInputProvider(packageConfig); |
- FormattingDiagnosticHandler diagnosticHandler = |
- new FormattingDiagnosticHandler(inputProvider); |
- IncrementalCompiler compiler = new IncrementalCompiler( |
- packageConfig: packageConfig, |
- inputProvider: inputProvider, |
- diagnosticHandler: diagnosticHandler, |
- outputProvider: new OutputProvider(), |
- support: incrementalMode, |
- platform: "fletch_mobile.platform"); |
- return new IncrementalTestHelper.internal( |
- packageConfig, |
- inputProvider, |
- compiler); |
- } |
- |
- Future<FletchDelta> fullCompile(ProgramResult program) async { |
- Map<String, String> code = computeCode(program); |
- inputProvider.sources.clear(); |
- code.forEach((String name, String code) { |
- inputProvider.sources[customUriBase.resolve(name)] = code; |
- }); |
- |
- await compiler.compile(customUriBase.resolve('main.dart'), customUriBase); |
- FletchDelta delta = compiler.compiler.context.backend.computeDelta(); |
- system = delta.system; |
- return delta; |
- } |
- |
- Future<FletchDelta> incrementalCompile( |
- ProgramResult program, |
- int version) async { |
- Map<String, String> code = computeCode(program); |
- Map<Uri, Uri> uriMap = <Uri, Uri>{}; |
- for (String name in code.keys) { |
- Uri uri = customUriBase.resolve('$name?v$version'); |
- inputProvider.cachedSources[uri] = new Future.value(code[name]); |
- uriMap[customUriBase.resolve(name)] = uri; |
- } |
- FletchDelta delta = await compiler.compileUpdates( |
- system, uriMap, logVerbose: logger, logTime: logger); |
- system = delta.system; |
- return delta; |
- } |
- |
- Map<String, String> computeCode(ProgramResult program) { |
- return program.code is String |
- ? <String,String>{ 'main.dart': program.code } |
- : program.code; |
- } |
-} |
- |
-/// An input provider which provides input via the class [File]. Includes |
-/// in-memory compilation units [sources] which are returned when a matching |
-/// key requested. |
-class IoInputProvider extends SourceFileProvider { |
- final Map<Uri, String> sources = <Uri, String>{}; |
- |
- final Uri packageConfig; |
- |
- final Map<Uri, Future> cachedSources = new Map<Uri, Future>(); |
- |
- static final Map<Uri, String> cachedFiles = new Map<Uri, String>(); |
- |
- IoInputProvider(this.packageConfig); |
- |
- Future readFromUri(Uri uri) { |
- return cachedSources.putIfAbsent(uri, () { |
- String text; |
- String name; |
- if (sources.containsKey(uri)) { |
- name = '$uri'; |
- text = sources[uri]; |
- } else { |
- if (uri.scheme == PACKAGE_SCHEME) { |
- throw "packages not supported $uri"; |
- } |
- text = readCachedFile(uri); |
- name = new File.fromUri(uri).path; |
- } |
- sourceFiles[uri] = new StringSourceFile(uri, name, text); |
- return new Future<String>.value(text); |
- }); |
- } |
- |
- Future call(Uri uri) => readStringFromUri(uri); |
- |
- Future<String> readStringFromUri(Uri uri) { |
- return readFromUri(uri); |
- } |
- |
- Future<List<int>> readUtf8BytesFromUri(Uri uri) { |
- throw "not supported"; |
- } |
- |
- static String readCachedFile(Uri uri) { |
- return cachedFiles.putIfAbsent( |
- uri, () => new File.fromUri(uri).readAsStringSync()); |
- } |
-} |