OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2017, the Dart 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 file. | |
4 | |
5 /// Integration test that runs the incremental compiler, runs the compiled | |
6 /// program, incrementally rebuild portions of the app, and triggers a hot | |
7 /// reload on the running program. | |
8 library front_end.incremental.hot_reload_e2e_test; | |
9 | |
10 import 'dart:async'; | |
11 import 'dart:convert'; | |
12 import 'dart:io'; | |
13 | |
14 import 'package:front_end/compiler_options.dart'; | |
15 import 'package:front_end/file_system.dart'; | |
16 import 'package:front_end/incremental_kernel_generator.dart'; | |
17 import 'package:front_end/memory_file_system.dart'; | |
18 import 'package:front_end/src/incremental/byte_store.dart'; | |
19 import 'package:front_end/src/testing/hybrid_file_system.dart'; | |
20 import 'package:front_end/src/vm/reload.dart'; | |
21 import 'package:kernel/ast.dart'; | |
22 import 'package:kernel/binary/limited_ast_to_binary.dart'; | |
23 import 'package:test/test.dart'; | |
24 | |
25 main() { | |
26 IncrementalKernelGenerator compiler; | |
27 MemoryFileSystem fs; | |
28 Directory outDir; | |
29 Uri outputUri; | |
30 List<Future<String>> lines; | |
31 Future programIsDone; | |
32 | |
33 setUp(() async { | |
34 outDir = Directory.systemTemp.createTempSync('hotreload_test'); | |
35 outputUri = outDir.uri.resolve('test.dill'); | |
36 fs = new MemoryFileSystem(Uri.parse('file:///')); | |
37 writeFile(fs, 'a.dart', sourceA); | |
38 writeFile(fs, 'b.dart', sourceB); | |
39 writeFile(fs, '.packages', ''); | |
40 compiler = await createIncrementalCompiler( | |
41 'file:///a.dart', new HybridFileSystem(fs)); | |
42 await rebuild(compiler, outputUri); // this is a full compile. | |
43 }); | |
44 | |
45 tearDown(() async { | |
46 outDir.deleteSync(recursive: true); | |
47 lines = null; | |
48 }); | |
49 | |
50 /// Start the VM with the first version of the program compiled by the | |
51 /// incremental compiler. | |
52 startProgram(args) async { | |
scheglov
2017/06/07 00:27:23
Please type "args".
| |
53 var vmArgs = [ | |
54 '--enable-vm-service=0', // Note: use 0 to avoid port collsions. | |
scheglov
2017/06/07 00:27:23
collisions
| |
55 '--platform=${platformFile.toFilePath()}', | |
56 outputUri.toFilePath() | |
57 ]; | |
58 vmArgs.addAll(args); | |
59 var vm = await Process.start(Platform.executable, vmArgs); | |
60 var splitter = new LineSplitter(); | |
61 | |
62 /// All tests below expect at most 3 events: reading the observatory port, | |
63 /// the output before waiting for a reload, and the output after the reload. | |
64 /// Each is represented by a single line in the output. | |
65 int i = 0; | |
66 var completers = new List.generate(3, (_) => new Completer<String>()); | |
67 lines = completers.map((c) => c.future).toList(); | |
68 vm.stdout.transform(UTF8.decoder).transform(splitter).listen((line) { | |
69 expect(i, lessThan(3)); | |
70 completers[i++].complete(line); | |
71 }); | |
72 | |
73 vm.stderr.transform(UTF8.decoder).transform(splitter).toList().then((err) { | |
74 expect(err, isEmpty, reason: err.join('\n')); | |
75 }); | |
76 | |
77 programIsDone = vm.exitCode; | |
78 } | |
79 | |
80 /// Request a hot reload on the running program. | |
81 hotReload() async { | |
82 var portLine = await lines[0]; | |
83 expect(observatoryPortRegExp.hasMatch(portLine), isTrue); | |
84 var match = observatoryPortRegExp.firstMatch(portLine); | |
85 var port = int.parse(match.group(1)); | |
86 var reloader = new VmReloader(port); | |
87 var reloadResult = await reloader.reload(outputUri); | |
88 expect(reloadResult['success'], isTrue); | |
89 await reloader?.disconnect(); | |
90 } | |
91 | |
92 test('initial program is valid', () async { | |
93 await startProgram(["2"]); | |
scheglov
2017/06/07 00:27:23
What means ["2"] arguments here?
Just a signal to
| |
94 await programIsDone; | |
95 expect(await lines.skip(1).first, "part1 part2"); | |
96 }); | |
97 | |
98 test('reload after leaf library modification', () async { | |
99 await startProgram([]); | |
100 expect(await lines[1], "part1 part2"); | |
101 | |
102 writeFile(fs, 'b.dart', sourceB.replaceAll("part1", "part3")); | |
103 await rebuild(compiler, outputUri); | |
104 await hotReload(); | |
105 await programIsDone; | |
106 expect(await lines[2], "part3 part2"); | |
107 }); | |
108 | |
109 test('reload after non-leaf library modification', () async { | |
110 await startProgram([]); | |
111 expect(await lines[1], "part1 part2"); | |
112 | |
113 writeFile(fs, 'a.dart', sourceA.replaceAll("part2", "part4")); | |
114 await rebuild(compiler, outputUri); | |
115 await hotReload(); | |
116 await programIsDone; | |
117 expect(await lines[2], "part1 part4"); | |
118 }, skip: true /* VM crashes on reload */); | |
Siggi Cherem (dart-lang)
2017/06/06 22:44:07
Siva: I forgot to mention, to reproduce the failur
| |
119 | |
120 test('reload after whole program modification', () async { | |
121 await startProgram([]); | |
122 expect(await lines[1], "part1 part2"); | |
123 | |
124 await hotReload(); | |
125 | |
126 writeFile(fs, 'b.dart', sourceB.replaceAll("part1", "part5")); | |
127 writeFile(fs, 'a.dart', sourceA.replaceAll("part2", "part6")); | |
128 await rebuild(compiler, outputUri); | |
129 await hotReload(); | |
130 await programIsDone; | |
131 expect(await lines[2], "part5 part6"); | |
132 }); | |
scheglov
2017/06/07 00:27:23
It might be interesting to have a test with more t
| |
133 } | |
134 | |
135 var dartVm = Uri.base.resolve(Platform.resolvedExecutable); | |
136 var sdkRoot = dartVm.resolve("patched_sdk/"); | |
137 var platformFile = sdkRoot.resolve('platform.dill'); | |
138 | |
139 Future<IncrementalKernelGenerator> createIncrementalCompiler( | |
140 String entry, FileSystem fs) { | |
141 var entryUri = Uri.base.resolve(entry); | |
142 var options = new CompilerOptions() | |
143 ..sdkRoot = sdkRoot | |
144 ..sdkSummary = sdkRoot.resolve('outline.dill') | |
145 ..packagesFileUri = Uri.parse('file:///.packages') | |
146 ..strongMode = false | |
147 ..dartLibraries = loadDartLibraries() | |
148 ..fileSystem = fs | |
149 ..byteStore = new MemoryByteStore(); | |
150 return IncrementalKernelGenerator.newInstance(options, entryUri); | |
151 } | |
152 | |
153 Map<String, Uri> loadDartLibraries() { | |
154 var libraries = sdkRoot.resolve('lib/libraries.json'); | |
155 var map = | |
156 JSON.decode(new File.fromUri(libraries).readAsStringSync())['libraries']; | |
157 var dartLibraries = <String, Uri>{}; | |
158 map.forEach((k, v) => dartLibraries[k] = libraries.resolve(v)); | |
159 return dartLibraries; | |
160 } | |
161 | |
162 Future<bool> rebuild(IncrementalKernelGenerator compiler, Uri outputUri) async { | |
163 compiler.invalidate(Uri.parse("file:///a.dart")); | |
164 compiler.invalidate(Uri.parse("file:///b.dart")); | |
165 var program = (await compiler.computeDelta()).newProgram; | |
166 if (program != null && !program.libraries.isEmpty) { | |
167 await writeProgram(program, outputUri); | |
168 return true; | |
169 } | |
170 return false; | |
171 } | |
172 | |
173 writeProgram(Program program, Uri outputUri) async { | |
174 // TODO(sigmund): the incremental generator should set the main method | |
175 // directly. | |
176 var mainLib = program.libraries.last; | |
177 program.mainMethod = | |
178 mainLib.procedures.firstWhere((p) => p.name.name == 'main'); | |
179 var sink = new File.fromUri(outputUri).openWrite(); | |
scheglov
2017/06/07 00:27:23
I can do this.
| |
180 | |
181 // TODO(sigmund): the incremental generator should always filter these | |
182 // libraries instead. | |
scheglov
2017/06/07 00:27:23
Is it a requirement for any kernel generation or f
| |
183 new LimitedBinaryPrinter( | |
184 sink, (library) => library.importUri.scheme != 'dart') | |
185 .writeProgramFile(program); | |
186 await sink.close(); | |
187 } | |
188 | |
189 writeFile(MemoryFileSystem fs, String fileName, String contents) { | |
scheglov
2017/06/07 00:27:23
void?
| |
190 fs.entityForUri(Uri.parse('file:///$fileName')).writeAsStringSync(contents); | |
191 } | |
192 | |
193 /// This program calls a function periodically and exits when the function | |
194 /// returns a different value than before (which only happens after a | |
195 /// hot-reload). | |
196 const sourceA = r''' | |
197 import 'dart:async'; | |
198 import 'b.dart'; | |
199 | |
200 main(args) { | |
201 var last = f(); | |
202 print(last); | |
203 | |
204 // Use any arguments to indicate that we don't want to wait for a | |
205 // hot-reload request. | |
206 if (args.length > 0) return; | |
207 | |
208 new Timer.periodic(new Duration(milliseconds: 100), (timer) { | |
209 var result = f(); | |
210 if (last != result) { | |
211 print(result); | |
212 timer.cancel(); | |
213 } | |
214 }); | |
215 } | |
216 | |
217 f() => "$line part2"; | |
218 '''; | |
219 | |
220 const sourceB = r''' | |
221 get line => "part1"; | |
222 '''; | |
223 | |
224 RegExp observatoryPortRegExp = | |
225 new RegExp("Observatory listening on http://127.0.0.1:\([0-9]*\)/"); | |
OLD | NEW |