Chromium Code Reviews| 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 |