| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE.md file. | |
| 4 | |
| 5 /// Mock VM implementation used for testing VM connections. | |
| 6 library fletchc.test.client.mock_vm; | |
| 7 | |
| 8 import 'dart:async' show | |
| 9 Future, | |
| 10 StreamIterator; | |
| 11 | |
| 12 import 'dart:io' show | |
| 13 InternetAddress, | |
| 14 ServerSocket, | |
| 15 Socket; | |
| 16 | |
| 17 import 'dart:isolate' show | |
| 18 ReceivePort, | |
| 19 SendPort; | |
| 20 | |
| 21 import 'dart:typed_data' show | |
| 22 ByteData; | |
| 23 | |
| 24 import 'package:fletchc/src/shared_command_infrastructure.dart' show | |
| 25 CommandTransformerBuilder, | |
| 26 toUint8ListView; | |
| 27 | |
| 28 import 'package:fletchc/vm_commands.dart' show | |
| 29 VmCommand, | |
| 30 VmCommandCode, | |
| 31 CommitChangesResult, | |
| 32 HandShakeResult, | |
| 33 ProcessTerminated; | |
| 34 | |
| 35 import 'package:dart_isolate/ports.dart' show | |
| 36 singleResponseFuture; | |
| 37 | |
| 38 import 'package:dart_isolate/isolate_runner.dart' show | |
| 39 IsolateRunner; | |
| 40 | |
| 41 import 'package:fletchc/src/zone_helper.dart' show | |
| 42 runGuarded; | |
| 43 | |
| 44 /// Represents state associated with a mocked VM. | |
| 45 class MockVm { | |
| 46 /// Port number the mock VM is listening on. Host is always 127.0.0.1. | |
| 47 final int port; | |
| 48 | |
| 49 /// Future completes with VM's exit code. | |
| 50 final Future<int> exitCode; | |
| 51 | |
| 52 // Internal. | |
| 53 final IsolateRunner isolate; | |
| 54 | |
| 55 MockVm(this.port, this.isolate, this.exitCode); | |
| 56 | |
| 57 /// Create a new MockVm. | |
| 58 /// If [closeImmediately] is true, the mock VM will close its socket | |
| 59 /// immediately after accepting an incoming connection. | |
| 60 /// If [closeAfterFirst] is non-null, the mock VM will close its socket | |
| 61 /// immediately after receiving a command with that code. | |
| 62 /// [closeImmediately] and [closeAfterFirst] can't be used together. | |
| 63 static Future<MockVm> spawn( | |
| 64 {bool closeImmediately: false, | |
| 65 VmCommandCode closeAfterFirst}) async { | |
| 66 if (closeImmediately && closeAfterFirst != null) { | |
| 67 throw new ArgumentError( | |
| 68 "[closeImmediately] and [closeAfterFirst] can't be used together"); | |
| 69 } | |
| 70 IsolateRunner isolate = await IsolateRunner.spawn(); | |
| 71 Future exitCode; | |
| 72 ReceivePort stdout = new ReceivePort(); | |
| 73 stdout.listen(print); | |
| 74 int port = await singleResponseFuture((SendPort port) { | |
| 75 int index = closeAfterFirst == null ? -1 : closeAfterFirst.index; | |
| 76 var arguments = new MockVmArguments( | |
| 77 port, closeImmediately, index, stdout.sendPort); | |
| 78 exitCode = isolate.run(mockVm, arguments) | |
| 79 .whenComplete(stdout.close) | |
| 80 .whenComplete(isolate.close); | |
| 81 }); | |
| 82 return new MockVm(port, isolate, exitCode); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 /// Encodes arguments to [mockVm]. | |
| 87 class MockVmArguments { | |
| 88 // Keep this class simple. See notice in `SharedTask` in | |
| 89 // `package:fletchc/src/verbs/infrastructure.dart`. | |
| 90 | |
| 91 final SendPort port; | |
| 92 final bool closeImmediately; | |
| 93 final int closeAfterFirstIndex; | |
| 94 final SendPort stdout; | |
| 95 | |
| 96 const MockVmArguments( | |
| 97 this.port, | |
| 98 this.closeImmediately, | |
| 99 this.closeAfterFirstIndex, | |
| 100 this.stdout); | |
| 101 | |
| 102 VmCommandCode get closeAfterFirst { | |
| 103 return closeAfterFirstIndex == -1 | |
| 104 ? null : VmCommandCode.values[closeAfterFirstIndex]; | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 /// See [MockVm.spawn]. | |
| 109 Future<int> mockVm(MockVmArguments arguments) async { | |
| 110 int exitCode = 0; | |
| 111 await runGuarded(() async { | |
| 112 Socket socket = await compilerConnection(arguments.port); | |
| 113 if (arguments.closeImmediately) { | |
| 114 socket.listen(null).cancel(); | |
| 115 await socket.close(); | |
| 116 return; | |
| 117 } | |
| 118 var transformer = new MockCommandTransformerBuilder().build(); | |
| 119 await for (var command in socket.transform(transformer)) { | |
| 120 VmCommandCode code = command[0]; | |
| 121 if (arguments.closeAfterFirst == code) break; | |
| 122 VmCommand reply = mockReply(code); | |
| 123 if (reply == null) { | |
| 124 print(command); | |
| 125 } else { | |
| 126 reply.addTo(socket); | |
| 127 await socket.flush(); | |
| 128 } | |
| 129 } | |
| 130 await socket.close(); | |
| 131 }, printLineOnStdout: arguments.stdout.send); | |
| 132 return exitCode; | |
| 133 } | |
| 134 | |
| 135 /// Transform List<int> (socket datagrams) to a two-element list of | |
| 136 /// `[VmCommandCode, payload]`. | |
| 137 class MockCommandTransformerBuilder extends CommandTransformerBuilder<List> { | |
| 138 List makeCommand(int code, ByteData payload) { | |
| 139 return [VmCommandCode.values[code], toUint8ListView(payload)]; | |
| 140 } | |
| 141 } | |
| 142 | |
| 143 /// Listens for a single connection from a compiler/debugger/etc. When the | |
| 144 /// server is started, its port number is returned on [port] letting a listener | |
| 145 /// know that the server is ready and listening for a connection. | |
| 146 Future<Socket> compilerConnection(SendPort port) async { | |
| 147 ServerSocket server = | |
| 148 await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0); | |
| 149 port.send(server.port); | |
| 150 var connectionIterator = new StreamIterator(server); | |
| 151 if (!await connectionIterator.moveNext()) { | |
| 152 throw "ServerSocket didn't get a connection"; | |
| 153 } | |
| 154 Socket socket = connectionIterator.current; | |
| 155 await server.close(); | |
| 156 while (await connectionIterator.moveNext()) { | |
| 157 throw "unexpected connection: ${connectionIterator.current}"; | |
| 158 } | |
| 159 return socket; | |
| 160 } | |
| 161 | |
| 162 /// Mock a reply if [code] requires a response for the compiler/debugger to | |
| 163 /// make progress. | |
| 164 VmCommand mockReply(VmCommandCode code) { | |
| 165 // Please add more cases as needed. | |
| 166 switch (code) { | |
| 167 case VmCommandCode.HandShake: | |
| 168 return new HandShakeResult(true, ""); | |
| 169 | |
| 170 case VmCommandCode.CommitChanges: | |
| 171 return new CommitChangesResult( | |
| 172 true, "Successfully applied program update."); | |
| 173 | |
| 174 case VmCommandCode.ProcessRun: | |
| 175 return const ProcessTerminated(); | |
| 176 | |
| 177 default: | |
| 178 return null; | |
| 179 } | |
| 180 } | |
| OLD | NEW |