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 |