OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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 library dev_compiler.test.dependency_graph_test; | |
6 | |
7 import 'package:analyzer/file_system/file_system.dart'; | |
8 import 'package:analyzer/file_system/memory_file_system.dart'; | |
9 import 'package:analyzer/src/generated/source.dart'; | |
10 import 'package:path/path.dart' as path; | |
11 import 'package:test/test.dart'; | |
12 | |
13 import 'package:dev_compiler/src/analysis_context.dart'; | |
14 import 'package:dev_compiler/src/compiler.dart'; | |
15 import 'package:dev_compiler/src/options.dart'; | |
16 import 'package:dev_compiler/src/report.dart'; | |
17 import 'package:dev_compiler/src/server/dependency_graph.dart'; | |
18 | |
19 import 'testing.dart'; | |
20 | |
21 void main() { | |
22 var options = new CompilerOptions( | |
23 runtimeDir: '/dev_compiler_runtime/', | |
24 sourceOptions: new SourceResolverOptions(useMockSdk: true)); | |
25 MemoryResourceProvider testResourceProvider; | |
26 ResourceUriResolver testUriResolver; | |
27 var context; | |
28 var graph; | |
29 | |
30 /// Initial values for test files | |
31 var testFiles = { | |
32 '/index1.html': ''' | |
33 <script src="foo.js"></script> | |
34 ''', | |
35 '/index2.html': ''' | |
36 <script type="application/dart" src="a1.dart"></script> | |
37 ''', | |
38 '/index3.html': ''' | |
39 <script type="application/dart" src="a2.dart"></script> | |
40 ''', | |
41 '/a1.dart': ''' | |
42 library a1; | |
43 ''', | |
44 '/a2.dart': ''' | |
45 library a2; | |
46 import 'a3.dart'; | |
47 import 'a4.dart'; | |
48 export 'a5.dart'; | |
49 part 'a6.dart'; | |
50 ''', | |
51 '/a3.dart': 'library a3;', | |
52 '/a4.dart': 'library a4; export "a10.dart";', | |
53 '/a5.dart': 'library a5;', | |
54 '/a6.dart': 'part of a2;', | |
55 '/a7.dart': 'library a7;', | |
56 '/a8.dart': 'library a8; import "a8.dart";', | |
57 '/a9.dart': 'library a9; import "a8.dart";', | |
58 '/a10.dart': 'library a10;', | |
59 }; | |
60 | |
61 nodeOf(String filepath) => graph.nodeFromUri(new Uri.file(filepath)); | |
62 | |
63 setUp(() { | |
64 /// We completely reset the TestUriResolver to avoid interference between | |
65 /// tests (since some tests modify the state of the files). | |
66 testResourceProvider = createTestResourceProvider(testFiles); | |
67 testUriResolver = new ResourceUriResolver(testResourceProvider); | |
68 context = createAnalysisContextWithSources(options.sourceOptions, | |
69 fileResolvers: [testUriResolver]); | |
70 graph = new SourceGraph(context, new LogReporter(context), options); | |
71 }); | |
72 | |
73 updateFile(Source source, [String newContents]) { | |
74 var path = testResourceProvider.pathContext.fromUri(source.uri); | |
75 if (newContents == null) newContents = source.contents.data; | |
76 testResourceProvider.updateFile(path, newContents); | |
77 } | |
78 | |
79 group('HTML deps', () { | |
80 test('initial deps', () { | |
81 var i1 = nodeOf('/index1.html'); | |
82 var i2 = nodeOf('/index2.html'); | |
83 expect(i1.scripts.length, 0); | |
84 expect(i2.scripts.length, 0); | |
85 i1.update(); | |
86 i2.update(); | |
87 expect(i1.scripts.length, 0); | |
88 expect(i2.scripts.length, 1); | |
89 expect(i2.scripts.first, nodeOf('/a1.dart')); | |
90 }); | |
91 | |
92 test('add a dep', () { | |
93 // After initial load, dependencies are 0: | |
94 var node = nodeOf('/index1.html'); | |
95 node.update(); | |
96 expect(node.scripts.length, 0); | |
97 | |
98 // Adding the dependency is discovered on the next round of updates: | |
99 updateFile(node.source, | |
100 '<script type="application/dart" src="a2.dart"></script>'); | |
101 expect(node.scripts.length, 0); | |
102 node.update(); | |
103 expect(node.scripts.length, 1); | |
104 expect(node.scripts.first, nodeOf('/a2.dart')); | |
105 }); | |
106 | |
107 test('add more deps', () { | |
108 // After initial load, dependencies are 1: | |
109 var node = nodeOf('/index2.html'); | |
110 node.update(); | |
111 expect(node.scripts.length, 1); | |
112 expect(node.scripts.first, nodeOf('/a1.dart')); | |
113 | |
114 updateFile( | |
115 node.source, | |
116 node.source.contents.data + | |
117 '<script type="application/dart" src="a2.dart"></script>'); | |
118 expect(node.scripts.length, 1); | |
119 node.update(); | |
120 expect(node.scripts.length, 2); | |
121 expect(node.scripts.first, nodeOf('/a1.dart')); | |
122 expect(node.scripts.last, nodeOf('/a2.dart')); | |
123 }); | |
124 | |
125 test('remove all deps', () { | |
126 // After initial load, dependencies are 1: | |
127 var node = nodeOf('/index2.html'); | |
128 node.update(); | |
129 expect(node.scripts.length, 1); | |
130 expect(node.scripts.first, nodeOf('/a1.dart')); | |
131 | |
132 // Removing the dependency is discovered on the next round of updates: | |
133 updateFile(node.source, ''); | |
134 expect(node.scripts.length, 1); | |
135 node.update(); | |
136 expect(node.scripts.length, 0); | |
137 }); | |
138 }); | |
139 | |
140 group('Dart deps', () { | |
141 test('initial deps', () { | |
142 var a1 = nodeOf('/a1.dart'); | |
143 var a2 = nodeOf('/a2.dart'); | |
144 expect(a1.imports.length, 0); | |
145 expect(a1.exports.length, 0); | |
146 expect(a1.parts.length, 0); | |
147 expect(a2.imports.length, 0); | |
148 expect(a2.exports.length, 0); | |
149 expect(a2.parts.length, 0); | |
150 | |
151 a1.update(); | |
152 a2.update(); | |
153 | |
154 expect(a1.imports.length, 0); | |
155 expect(a1.exports.length, 0); | |
156 expect(a1.parts.length, 0); | |
157 expect(a2.imports.length, 2); | |
158 expect(a2.exports.length, 1); | |
159 expect(a2.parts.length, 1); | |
160 expect(a2.imports.contains(nodeOf('/a3.dart')), isTrue); | |
161 expect(a2.imports.contains(nodeOf('/a4.dart')), isTrue); | |
162 expect(a2.exports.contains(nodeOf('/a5.dart')), isTrue); | |
163 expect(a2.parts.contains(nodeOf('/a6.dart')), isTrue); | |
164 }); | |
165 | |
166 test('add deps', () { | |
167 var node = nodeOf('/a1.dart'); | |
168 node.update(); | |
169 expect(node.imports.length, 0); | |
170 expect(node.exports.length, 0); | |
171 expect(node.parts.length, 0); | |
172 | |
173 updateFile( | |
174 node.source, 'import "a3.dart"; export "a5.dart"; part "a8.dart";'); | |
175 node.update(); | |
176 | |
177 expect(node.imports.length, 1); | |
178 expect(node.exports.length, 1); | |
179 expect(node.parts.length, 1); | |
180 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
181 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
182 expect(node.parts.contains(nodeOf('/a8.dart')), isTrue); | |
183 }); | |
184 | |
185 test('remove deps', () { | |
186 var node = nodeOf('/a2.dart'); | |
187 node.update(); | |
188 expect(node.imports.length, 2); | |
189 expect(node.exports.length, 1); | |
190 expect(node.parts.length, 1); | |
191 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
192 expect(node.imports.contains(nodeOf('/a4.dart')), isTrue); | |
193 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
194 expect(node.parts.contains(nodeOf('/a6.dart')), isTrue); | |
195 | |
196 updateFile( | |
197 node.source, 'import "a3.dart"; export "a7.dart"; part "a8.dart";'); | |
198 node.update(); | |
199 | |
200 expect(node.imports.length, 1); | |
201 expect(node.exports.length, 1); | |
202 expect(node.parts.length, 1); | |
203 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
204 expect(node.exports.contains(nodeOf('/a7.dart')), isTrue); | |
205 expect(node.parts.contains(nodeOf('/a8.dart')), isTrue); | |
206 }); | |
207 | |
208 test('change part to library', () { | |
209 var node = nodeOf('/a2.dart'); | |
210 node.update(); | |
211 expect(node.imports.length, 2); | |
212 expect(node.exports.length, 1); | |
213 expect(node.parts.length, 1); | |
214 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
215 expect(node.imports.contains(nodeOf('/a4.dart')), isTrue); | |
216 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
217 expect(node.parts.contains(nodeOf('/a6.dart')), isTrue); | |
218 | |
219 updateFile( | |
220 node.source, | |
221 ''' | |
222 library a2; | |
223 import 'a3.dart'; | |
224 import 'a4.dart'; | |
225 export 'a5.dart'; | |
226 import 'a6.dart'; // changed from part | |
227 '''); | |
228 var a6 = nodeOf('/a6.dart'); | |
229 updateFile(a6.source, ''); | |
230 node.update(); | |
231 | |
232 expect(node.imports.length, 3); | |
233 expect(node.exports.length, 1); | |
234 expect(node.parts.length, 0); | |
235 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
236 expect(node.imports.contains(nodeOf('/a4.dart')), isTrue); | |
237 expect(node.imports.contains(nodeOf('/a6.dart')), isTrue); | |
238 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
239 | |
240 expect(a6.imports.length, 0); | |
241 expect(a6.exports.length, 0); | |
242 expect(a6.parts.length, 0); | |
243 }); | |
244 | |
245 test('change library to part', () { | |
246 var node = nodeOf('/a2.dart'); | |
247 var a4 = nodeOf('/a4.dart'); | |
248 node.update(); | |
249 expect(node.imports.length, 2); | |
250 expect(node.exports.length, 1); | |
251 expect(node.parts.length, 1); | |
252 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
253 expect(node.imports.contains(nodeOf('/a4.dart')), isTrue); | |
254 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
255 expect(node.parts.contains(nodeOf('/a6.dart')), isTrue); | |
256 | |
257 a4.update(); | |
258 expect(a4.imports.length, 0); | |
259 expect(a4.exports.length, 1); | |
260 expect(a4.parts.length, 0); | |
261 | |
262 updateFile( | |
263 node.source, | |
264 ''' | |
265 library a2; | |
266 import 'a3.dart'; | |
267 part 'a4.dart'; // changed from export | |
268 export 'a5.dart'; | |
269 part 'a6.dart'; | |
270 '''); | |
271 node.update(); | |
272 | |
273 expect(node.imports.length, 1); | |
274 expect(node.exports.length, 1); | |
275 expect(node.parts.length, 2); | |
276 expect(node.imports.contains(nodeOf('/a3.dart')), isTrue); | |
277 expect(node.exports.contains(nodeOf('/a5.dart')), isTrue); | |
278 expect(node.parts.contains(nodeOf('/a4.dart')), isTrue); | |
279 expect(node.parts.contains(nodeOf('/a6.dart')), isTrue); | |
280 | |
281 // Note, technically we never modified the contents of a4 and it contains | |
282 // an export. This is invalid Dart, but we'll let the analyzer report that | |
283 // error instead of doing so ourselves. | |
284 expect(a4.imports.length, 0); | |
285 expect(a4.exports.length, 1); | |
286 expect(a4.parts.length, 0); | |
287 | |
288 // And change it back. | |
289 updateFile( | |
290 node.source, | |
291 ''' | |
292 library a2; | |
293 import 'a3.dart'; | |
294 import 'a4.dart'; // changed again | |
295 export 'a5.dart'; | |
296 part 'a6.dart'; | |
297 '''); | |
298 node.update(); | |
299 expect(node.imports.contains(a4), isTrue); | |
300 expect(a4.imports.length, 0); | |
301 expect(a4.exports.length, 1); | |
302 expect(a4.parts.length, 0); | |
303 }); | |
304 }); | |
305 | |
306 group('local changes', () { | |
307 group('needs rebuild', () { | |
308 test('in HTML', () { | |
309 var node = nodeOf('/index1.html'); | |
310 node.update(); | |
311 expect(node.needsRebuild, isTrue); | |
312 node.needsRebuild = false; | |
313 | |
314 node.update(); | |
315 expect(node.needsRebuild, isFalse); | |
316 | |
317 // For now, an empty modification is enough to trigger a rebuild | |
318 updateFile(node.source); | |
319 expect(node.needsRebuild, isFalse); | |
320 node.update(); | |
321 expect(node.needsRebuild, isTrue); | |
322 }); | |
323 | |
324 test('main library in Dart', () { | |
325 var node = nodeOf('/a2.dart'); | |
326 var partNode = nodeOf('/a6.dart'); | |
327 node.update(); | |
328 expect(node.needsRebuild, isTrue); | |
329 node.needsRebuild = false; | |
330 partNode.needsRebuild = false; | |
331 | |
332 node.update(); | |
333 expect(node.needsRebuild, isFalse); | |
334 | |
335 // For now, an empty modification is enough to trigger a rebuild | |
336 updateFile(node.source); | |
337 expect(node.needsRebuild, isFalse); | |
338 node.update(); | |
339 expect(node.needsRebuild, isTrue); | |
340 }); | |
341 | |
342 test('part of library in Dart', () { | |
343 var node = nodeOf('/a2.dart'); | |
344 var importNode = nodeOf('/a3.dart'); | |
345 var exportNode = nodeOf('/a5.dart'); | |
346 var partNode = nodeOf('/a6.dart'); | |
347 node.update(); | |
348 expect(node.needsRebuild, isTrue); | |
349 node.needsRebuild = false; | |
350 partNode.needsRebuild = false; | |
351 | |
352 node.update(); | |
353 expect(node.needsRebuild, isFalse); | |
354 | |
355 // Modification in imported/exported node makes no difference for local | |
356 // rebuild label (globally that's tested elsewhere) | |
357 updateFile(importNode.source); | |
358 updateFile(exportNode.source); | |
359 node.update(); | |
360 expect(node.needsRebuild, isFalse); | |
361 expect(partNode.needsRebuild, isFalse); | |
362 | |
363 // Modification in part triggers change in containing library: | |
364 updateFile(partNode.source); | |
365 expect(node.needsRebuild, isFalse); | |
366 expect(partNode.needsRebuild, isFalse); | |
367 node.update(); | |
368 expect(node.needsRebuild, isTrue); | |
369 expect(partNode.needsRebuild, isTrue); | |
370 }); | |
371 }); | |
372 | |
373 group('structure change', () { | |
374 test('no mod in HTML', () { | |
375 var node = nodeOf('/index2.html'); | |
376 node.update(); | |
377 expect(node.structureChanged, isTrue); | |
378 node.structureChanged = false; | |
379 | |
380 node.update(); | |
381 expect(node.structureChanged, isFalse); | |
382 | |
383 // An empty modification will not trigger a structural change | |
384 updateFile(node.source); | |
385 expect(node.structureChanged, isFalse); | |
386 node.update(); | |
387 expect(node.structureChanged, isFalse); | |
388 }); | |
389 | |
390 test('added scripts in HTML', () { | |
391 var node = nodeOf('/index2.html'); | |
392 node.update(); | |
393 expect(node.structureChanged, isTrue); | |
394 expect(node.scripts.length, 1); | |
395 | |
396 node.structureChanged = false; | |
397 node.update(); | |
398 expect(node.structureChanged, isFalse); | |
399 | |
400 // This change will not include new script tags: | |
401 updateFile(node.source, node.source.contents.data + '<div></div>'); | |
402 expect(node.structureChanged, isFalse); | |
403 node.update(); | |
404 expect(node.structureChanged, isFalse); | |
405 expect(node.scripts.length, 1); | |
406 | |
407 updateFile( | |
408 node.source, | |
409 node.source.contents.data + | |
410 '<script type="application/dart" src="a4.dart"></script>'); | |
411 expect(node.structureChanged, isFalse); | |
412 node.update(); | |
413 expect(node.structureChanged, isTrue); | |
414 expect(node.scripts.length, 2); | |
415 }); | |
416 | |
417 test('no mod in Dart', () { | |
418 var node = nodeOf('/a2.dart'); | |
419 var importNode = nodeOf('/a3.dart'); | |
420 var exportNode = nodeOf('/a5.dart'); | |
421 var partNode = nodeOf('/a6.dart'); | |
422 node.update(); | |
423 expect(node.structureChanged, isTrue); | |
424 node.structureChanged = false; | |
425 | |
426 node.update(); | |
427 expect(node.structureChanged, isFalse); | |
428 | |
429 // These modifications make no difference at all. | |
430 updateFile(importNode.source); | |
431 updateFile(exportNode.source); | |
432 updateFile(partNode.source); | |
433 updateFile(node.source); | |
434 | |
435 expect(node.structureChanged, isFalse); | |
436 node.update(); | |
437 expect(node.structureChanged, isFalse); | |
438 }); | |
439 | |
440 test('same directives, different order', () { | |
441 var node = nodeOf('/a2.dart'); | |
442 node.update(); | |
443 expect(node.structureChanged, isTrue); | |
444 node.structureChanged = false; | |
445 | |
446 node.update(); | |
447 expect(node.structureChanged, isFalse); | |
448 | |
449 // modified order of imports, but structure stays the same: | |
450 updateFile( | |
451 node.source, | |
452 'import "a4.dart"; import "a3.dart"; ' | |
453 'export "a5.dart"; part "a6.dart";'); | |
454 node.update(); | |
455 | |
456 expect(node.structureChanged, isFalse); | |
457 node.update(); | |
458 expect(node.structureChanged, isFalse); | |
459 }); | |
460 | |
461 test('changed parts', () { | |
462 var node = nodeOf('/a2.dart'); | |
463 node.update(); | |
464 expect(node.structureChanged, isTrue); | |
465 node.structureChanged = false; | |
466 | |
467 node.update(); | |
468 expect(node.structureChanged, isFalse); | |
469 | |
470 // added one. | |
471 updateFile( | |
472 node.source, | |
473 'import "a4.dart"; import "a3.dart"; ' | |
474 'export "a5.dart"; part "a6.dart"; part "a7.dart";'); | |
475 expect(node.structureChanged, isFalse); | |
476 node.update(); | |
477 expect(node.structureChanged, isTrue); | |
478 | |
479 // no change | |
480 node.structureChanged = false; | |
481 updateFile(node.source); | |
482 node.update(); | |
483 expect(node.structureChanged, isFalse); | |
484 | |
485 // removed one | |
486 updateFile(node.source); | |
487 updateFile( | |
488 node.source, | |
489 'import "a4.dart"; import "a3.dart"; ' | |
490 'export "a5.dart"; part "a7.dart";'); | |
491 expect(node.structureChanged, isFalse); | |
492 node.update(); | |
493 expect(node.structureChanged, isTrue); | |
494 }); | |
495 | |
496 test('changed import', () { | |
497 var node = nodeOf('/a2.dart'); | |
498 node.update(); | |
499 expect(node.structureChanged, isTrue); | |
500 node.structureChanged = false; | |
501 | |
502 node.update(); | |
503 expect(node.structureChanged, isFalse); | |
504 | |
505 // added one. | |
506 updateFile( | |
507 node.source, | |
508 'import "a4.dart"; import "a3.dart"; import "a7.dart";' | |
509 'export "a5.dart"; part "a6.dart";'); | |
510 expect(node.structureChanged, isFalse); | |
511 node.update(); | |
512 expect(node.structureChanged, isTrue); | |
513 | |
514 // no change | |
515 node.structureChanged = false; | |
516 updateFile(node.source); | |
517 node.update(); | |
518 expect(node.structureChanged, isFalse); | |
519 | |
520 // removed one | |
521 updateFile( | |
522 node.source, | |
523 'import "a4.dart"; import "a7.dart"; ' | |
524 'export "a5.dart"; part "a6.dart";'); | |
525 expect(node.structureChanged, isFalse); | |
526 node.update(); | |
527 expect(node.structureChanged, isTrue); | |
528 }); | |
529 | |
530 test('changed exports', () { | |
531 var node = nodeOf('/a2.dart'); | |
532 node.update(); | |
533 expect(node.structureChanged, isTrue); | |
534 node.structureChanged = false; | |
535 | |
536 node.update(); | |
537 expect(node.structureChanged, isFalse); | |
538 | |
539 // added one. | |
540 updateFile( | |
541 node.source, | |
542 'import "a4.dart"; import "a3.dart";' | |
543 'export "a5.dart"; export "a9.dart"; part "a6.dart";'); | |
544 expect(node.structureChanged, isFalse); | |
545 node.update(); | |
546 expect(node.structureChanged, isTrue); | |
547 | |
548 // no change | |
549 node.structureChanged = false; | |
550 updateFile(node.source); | |
551 node.update(); | |
552 expect(node.structureChanged, isFalse); | |
553 | |
554 // removed one | |
555 updateFile( | |
556 node.source, | |
557 'import "a4.dart"; import "a3.dart"; ' | |
558 'export "a5.dart"; part "a6.dart";'); | |
559 expect(node.structureChanged, isFalse); | |
560 node.update(); | |
561 expect(node.structureChanged, isTrue); | |
562 }); | |
563 }); | |
564 }); | |
565 | |
566 group('refresh structure and marks', () { | |
567 test('initial marks', () { | |
568 var node = nodeOf('/index3.html'); | |
569 expectGraph( | |
570 node, | |
571 ''' | |
572 index3.html | |
573 $_RUNTIME_GRAPH | |
574 '''); | |
575 refreshStructureAndMarks(node); | |
576 expectGraph( | |
577 node, | |
578 ''' | |
579 index3.html [needs-rebuild] [structure-changed] | |
580 |-- a2.dart [needs-rebuild] [structure-changed] | |
581 | |-- a3.dart [needs-rebuild] | |
582 | |-- a4.dart [needs-rebuild] [structure-changed] | |
583 | | |-- a10.dart [needs-rebuild] | |
584 | |-- a5.dart [needs-rebuild] | |
585 | |-- a6.dart (part) [needs-rebuild] | |
586 $_RUNTIME_GRAPH_REBUILD | |
587 '''); | |
588 }); | |
589 | |
590 test('cleared marks stay clear', () { | |
591 var node = nodeOf('/index3.html'); | |
592 refreshStructureAndMarks(node); | |
593 expectGraph( | |
594 node, | |
595 ''' | |
596 index3.html [needs-rebuild] [structure-changed] | |
597 |-- a2.dart [needs-rebuild] [structure-changed] | |
598 | |-- a3.dart [needs-rebuild] | |
599 | |-- a4.dart [needs-rebuild] [structure-changed] | |
600 | | |-- a10.dart [needs-rebuild] | |
601 | |-- a5.dart [needs-rebuild] | |
602 | |-- a6.dart (part) [needs-rebuild] | |
603 $_RUNTIME_GRAPH_REBUILD | |
604 '''); | |
605 clearMarks(node); | |
606 expectGraph( | |
607 node, | |
608 ''' | |
609 index3.html | |
610 |-- a2.dart | |
611 | |-- a3.dart | |
612 | |-- a4.dart | |
613 | | |-- a10.dart | |
614 | |-- a5.dart | |
615 | |-- a6.dart (part) | |
616 $_RUNTIME_GRAPH | |
617 '''); | |
618 | |
619 refreshStructureAndMarks(node); | |
620 expectGraph( | |
621 node, | |
622 ''' | |
623 index3.html | |
624 |-- a2.dart | |
625 | |-- a3.dart | |
626 | |-- a4.dart | |
627 | | |-- a10.dart | |
628 | |-- a5.dart | |
629 | |-- a6.dart (part) | |
630 $_RUNTIME_GRAPH | |
631 '''); | |
632 }); | |
633 | |
634 test('needsRebuild mark updated on local modifications', () { | |
635 var node = nodeOf('/index3.html'); | |
636 refreshStructureAndMarks(node); | |
637 clearMarks(node); | |
638 var a3 = nodeOf('/a3.dart'); | |
639 updateFile(a3.source); | |
640 | |
641 refreshStructureAndMarks(node); | |
642 expectGraph( | |
643 node, | |
644 ''' | |
645 index3.html | |
646 |-- a2.dart | |
647 | |-- a3.dart [needs-rebuild] | |
648 | |-- a4.dart | |
649 | | |-- a10.dart | |
650 | |-- a5.dart | |
651 | |-- a6.dart (part) | |
652 $_RUNTIME_GRAPH | |
653 '''); | |
654 }); | |
655 | |
656 test('structuredChanged mark updated on structure modifications', () { | |
657 var node = nodeOf('/index3.html'); | |
658 refreshStructureAndMarks(node); | |
659 clearMarks(node); | |
660 var a5 = nodeOf('/a5.dart'); | |
661 updateFile(a5.source, 'import "a8.dart";'); | |
662 | |
663 refreshStructureAndMarks(node); | |
664 expectGraph( | |
665 node, | |
666 ''' | |
667 index3.html | |
668 |-- a2.dart | |
669 | |-- a3.dart | |
670 | |-- a4.dart | |
671 | | |-- a10.dart | |
672 | |-- a5.dart [needs-rebuild] [structure-changed] | |
673 | | |-- a8.dart [needs-rebuild] [structure-changed] | |
674 | | | |-- a8.dart... | |
675 | |-- a6.dart (part) | |
676 $_RUNTIME_GRAPH | |
677 '''); | |
678 }); | |
679 }); | |
680 | |
681 group('server-mode', () { | |
682 setUp(() { | |
683 var opts = new CompilerOptions( | |
684 runtimeDir: '/dev_compiler_runtime/', | |
685 sourceOptions: new SourceResolverOptions(useMockSdk: true), | |
686 serverMode: true); | |
687 context = createAnalysisContextWithSources(opts.sourceOptions, | |
688 fileResolvers: [testUriResolver]); | |
689 graph = new SourceGraph(context, new LogReporter(context), opts); | |
690 }); | |
691 | |
692 test('messages widget is automatically included', () { | |
693 var node = nodeOf('/index3.html'); | |
694 expectGraph( | |
695 node, | |
696 ''' | |
697 index3.html | |
698 $_RUNTIME_GRAPH | |
699 |-- messages_widget.js | |
700 |-- messages.css | |
701 '''); | |
702 refreshStructureAndMarks(node); | |
703 expectGraph( | |
704 node, | |
705 ''' | |
706 index3.html [needs-rebuild] [structure-changed] | |
707 |-- a2.dart [needs-rebuild] [structure-changed] | |
708 | |-- a3.dart [needs-rebuild] | |
709 | |-- a4.dart [needs-rebuild] [structure-changed] | |
710 | | |-- a10.dart [needs-rebuild] | |
711 | |-- a5.dart [needs-rebuild] | |
712 | |-- a6.dart (part) [needs-rebuild] | |
713 $_RUNTIME_GRAPH_REBUILD | |
714 |-- messages_widget.js [needs-rebuild] | |
715 |-- messages.css [needs-rebuild] | |
716 '''); | |
717 }); | |
718 }); | |
719 | |
720 group('rebuild', () { | |
721 var results; | |
722 void addName(SourceNode n) => results.add(nameFor(n)); | |
723 | |
724 bool buildNoTransitiveChange(SourceNode n) { | |
725 addName(n); | |
726 return false; | |
727 } | |
728 | |
729 bool buildWithTransitiveChange(SourceNode n) { | |
730 addName(n); | |
731 return true; | |
732 } | |
733 | |
734 setUp(() { | |
735 results = []; | |
736 }); | |
737 | |
738 test('everything build on first run', () { | |
739 var node = nodeOf('/index3.html'); | |
740 rebuild(node, buildNoTransitiveChange); | |
741 // Note: a6.dart is not included because it built as part of a2.dart | |
742 expect( | |
743 results, | |
744 ['a3.dart', 'a10.dart', 'a4.dart', 'a5.dart', 'a2.dart'] | |
745 ..addAll(runtimeFilesWithoutPath) | |
746 ..add('index3.html')); | |
747 | |
748 // Marks are removed automatically by rebuild | |
749 expectGraph( | |
750 node, | |
751 ''' | |
752 index3.html | |
753 |-- a2.dart | |
754 | |-- a3.dart | |
755 | |-- a4.dart | |
756 | | |-- a10.dart | |
757 | |-- a5.dart | |
758 | |-- a6.dart (part) | |
759 $_RUNTIME_GRAPH | |
760 '''); | |
761 }); | |
762 | |
763 test('nothing to do after build', () { | |
764 var node = nodeOf('/index3.html'); | |
765 rebuild(node, buildNoTransitiveChange); | |
766 | |
767 results = []; | |
768 rebuild(node, buildNoTransitiveChange); | |
769 expect(results, []); | |
770 }); | |
771 | |
772 test('modified part triggers building library', () { | |
773 var node = nodeOf('/index3.html'); | |
774 rebuild(node, buildNoTransitiveChange); | |
775 results = []; | |
776 | |
777 var a6 = nodeOf('/a6.dart'); | |
778 updateFile(a6.source); | |
779 rebuild(node, buildNoTransitiveChange); | |
780 expect(results, ['a2.dart']); | |
781 | |
782 results = []; | |
783 rebuild(node, buildNoTransitiveChange); | |
784 expect(results, []); | |
785 }); | |
786 | |
787 test('non-API change triggers build stays local', () { | |
788 var node = nodeOf('/index3.html'); | |
789 rebuild(node, buildNoTransitiveChange); | |
790 results = []; | |
791 | |
792 var a3 = nodeOf('/a3.dart'); | |
793 updateFile(a3.source); | |
794 rebuild(node, buildNoTransitiveChange); | |
795 expect(results, ['a3.dart']); | |
796 | |
797 results = []; | |
798 rebuild(node, buildNoTransitiveChange); | |
799 expect(results, []); | |
800 }); | |
801 | |
802 test('no-API change in exported file stays local', () { | |
803 var node = nodeOf('/index3.html'); | |
804 rebuild(node, buildNoTransitiveChange); | |
805 results = []; | |
806 | |
807 // similar to the test above, but a10 is exported from a4. | |
808 var a3 = nodeOf('/a10.dart'); | |
809 updateFile(a3.source); | |
810 rebuild(node, buildNoTransitiveChange); | |
811 expect(results, ['a10.dart']); | |
812 | |
813 results = []; | |
814 rebuild(node, buildNoTransitiveChange); | |
815 expect(results, []); | |
816 }); | |
817 | |
818 test('API change in lib, triggers build on imports', () { | |
819 var node = nodeOf('/index3.html'); | |
820 rebuild(node, buildNoTransitiveChange); | |
821 results = []; | |
822 | |
823 var a3 = nodeOf('/a3.dart'); | |
824 updateFile(a3.source); | |
825 rebuild(node, buildWithTransitiveChange); | |
826 expect(results, ['a3.dart', 'a2.dart']); | |
827 | |
828 results = []; | |
829 rebuild(node, buildNoTransitiveChange); | |
830 expect(results, []); | |
831 }); | |
832 | |
833 test('API change in export, triggers build on imports', () { | |
834 var node = nodeOf('/index3.html'); | |
835 rebuild(node, buildNoTransitiveChange); | |
836 results = []; | |
837 | |
838 var a3 = nodeOf('/a10.dart'); | |
839 updateFile(a3.source); | |
840 rebuild(node, buildWithTransitiveChange); | |
841 | |
842 // Node: a4.dart reexports a10.dart, but it doesn't import it, so we don't | |
843 // need to rebuild it. | |
844 expect(results, ['a10.dart', 'a2.dart']); | |
845 | |
846 results = []; | |
847 rebuild(node, buildNoTransitiveChange); | |
848 expect(results, []); | |
849 }); | |
850 | |
851 test('structural change rebuilds HTML, but skips unreachable code', () { | |
852 var node = nodeOf('/index3.html'); | |
853 rebuild(node, buildNoTransitiveChange); | |
854 results = []; | |
855 | |
856 var a2 = nodeOf('/a2.dart'); | |
857 updateFile(a2.source, 'import "a4.dart";'); | |
858 | |
859 var a3 = nodeOf('/a3.dart'); | |
860 updateFile(a3.source); | |
861 rebuild(node, buildNoTransitiveChange); | |
862 | |
863 // a3 will become unreachable, index3 reflects structural changes. | |
864 expect(results, ['a2.dart', 'index3.html']); | |
865 | |
866 results = []; | |
867 rebuild(node, buildNoTransitiveChange); | |
868 expect(results, []); | |
869 }); | |
870 | |
871 test('newly discovered files get built too', () { | |
872 var node = nodeOf('/index3.html'); | |
873 rebuild(node, buildNoTransitiveChange); | |
874 results = []; | |
875 | |
876 var a2 = nodeOf('/a2.dart'); | |
877 updateFile(a2.source, 'import "a9.dart";'); | |
878 | |
879 rebuild(node, buildNoTransitiveChange); | |
880 expect(results, ['a8.dart', 'a9.dart', 'a2.dart', 'index3.html']); | |
881 | |
882 results = []; | |
883 rebuild(node, buildNoTransitiveChange); | |
884 expect(results, []); | |
885 }); | |
886 | |
887 group('file upgrades', () { | |
888 // Normally upgrading involves two changes: | |
889 // (a) change the affected file | |
890 // (b) change directive from part to import (or viceversa) | |
891 // These could happen in any order and we should reach a consistent state | |
892 // in the end. | |
893 | |
894 test('convert part to a library before updating the import', () { | |
895 var node = nodeOf('/index3.html'); | |
896 var a2 = nodeOf('/a2.dart'); | |
897 var a6 = nodeOf('/a6.dart'); | |
898 rebuild(node, buildNoTransitiveChange); | |
899 | |
900 expectGraph( | |
901 node, | |
902 ''' | |
903 index3.html | |
904 |-- a2.dart | |
905 | |-- a3.dart | |
906 | |-- a4.dart | |
907 | | |-- a10.dart | |
908 | |-- a5.dart | |
909 | |-- a6.dart (part) | |
910 $_RUNTIME_GRAPH | |
911 '''); | |
912 | |
913 // Modify the file first: | |
914 updateFile(a6.source, 'library a6; import "a5.dart";'); | |
915 results = []; | |
916 rebuild(node, buildNoTransitiveChange); | |
917 | |
918 // Looks to us like a change in a part, we'll report errors that the | |
919 // part is not really a part-file. Note that a6.dart is not included | |
920 // below, because we don't build it as a library. | |
921 expect(results, ['a2.dart']); | |
922 expectGraph( | |
923 node, | |
924 ''' | |
925 index3.html | |
926 |-- a2.dart | |
927 | |-- a3.dart | |
928 | |-- a4.dart | |
929 | | |-- a10.dart | |
930 | |-- a5.dart | |
931 | |-- a6.dart (part) | |
932 $_RUNTIME_GRAPH | |
933 '''); | |
934 | |
935 updateFile( | |
936 a2.source, | |
937 ''' | |
938 library a2; | |
939 import 'a3.dart'; | |
940 import 'a4.dart'; | |
941 import 'a6.dart'; // properly import it | |
942 export 'a5.dart'; | |
943 '''); | |
944 results = []; | |
945 rebuild(node, buildNoTransitiveChange); | |
946 // Note that a6 is now included, because we haven't built it as a | |
947 // library until now: | |
948 expect(results, ['a6.dart', 'a2.dart', 'index3.html']); | |
949 | |
950 updateFile(a6.source); | |
951 results = []; | |
952 rebuild(node, buildNoTransitiveChange); | |
953 expect(results, ['a6.dart']); | |
954 | |
955 expectGraph( | |
956 node, | |
957 ''' | |
958 index3.html | |
959 |-- a2.dart | |
960 | |-- a3.dart | |
961 | |-- a4.dart | |
962 | | |-- a10.dart | |
963 | |-- a6.dart | |
964 | | |-- a5.dart | |
965 | |-- a5.dart... | |
966 $_RUNTIME_GRAPH | |
967 '''); | |
968 }); | |
969 | |
970 test('convert part to a library after updating the import', () { | |
971 var node = nodeOf('/index3.html'); | |
972 var a2 = nodeOf('/a2.dart'); | |
973 var a6 = nodeOf('/a6.dart'); | |
974 rebuild(node, buildNoTransitiveChange); | |
975 | |
976 expectGraph( | |
977 node, | |
978 ''' | |
979 index3.html | |
980 |-- a2.dart | |
981 | |-- a3.dart | |
982 | |-- a4.dart | |
983 | | |-- a10.dart | |
984 | |-- a5.dart | |
985 | |-- a6.dart (part) | |
986 $_RUNTIME_GRAPH | |
987 '''); | |
988 | |
989 updateFile( | |
990 a2.source, | |
991 ''' | |
992 library a2; | |
993 import 'a3.dart'; | |
994 import 'a4.dart'; | |
995 import 'a6.dart'; // properly import it | |
996 export 'a5.dart'; | |
997 '''); | |
998 results = []; | |
999 rebuild(node, buildNoTransitiveChange); | |
1000 expect(results, ['a6.dart', 'a2.dart', 'index3.html']); | |
1001 expectGraph( | |
1002 node, | |
1003 ''' | |
1004 index3.html | |
1005 |-- a2.dart | |
1006 | |-- a3.dart | |
1007 | |-- a4.dart | |
1008 | | |-- a10.dart | |
1009 | |-- a6.dart | |
1010 | |-- a5.dart | |
1011 $_RUNTIME_GRAPH | |
1012 '''); | |
1013 | |
1014 updateFile(a6.source, 'library a6; import "a5.dart";'); | |
1015 results = []; | |
1016 rebuild(node, buildNoTransitiveChange); | |
1017 expect(results, ['a6.dart', 'index3.html']); | |
1018 expectGraph( | |
1019 node, | |
1020 ''' | |
1021 index3.html | |
1022 |-- a2.dart | |
1023 | |-- a3.dart | |
1024 | |-- a4.dart | |
1025 | | |-- a10.dart | |
1026 | |-- a6.dart | |
1027 | | |-- a5.dart | |
1028 | |-- a5.dart... | |
1029 $_RUNTIME_GRAPH | |
1030 '''); | |
1031 }); | |
1032 | |
1033 test('disconnect part making it a library', () { | |
1034 var node = nodeOf('/index3.html'); | |
1035 var a2 = nodeOf('/a2.dart'); | |
1036 var a6 = nodeOf('/a6.dart'); | |
1037 rebuild(node, buildNoTransitiveChange); | |
1038 | |
1039 expectGraph( | |
1040 node, | |
1041 ''' | |
1042 index3.html | |
1043 |-- a2.dart | |
1044 | |-- a3.dart | |
1045 | |-- a4.dart | |
1046 | | |-- a10.dart | |
1047 | |-- a5.dart | |
1048 | |-- a6.dart (part) | |
1049 $_RUNTIME_GRAPH | |
1050 '''); | |
1051 | |
1052 updateFile( | |
1053 a2.source, | |
1054 ''' | |
1055 library a2; | |
1056 import 'a3.dart'; | |
1057 import 'a4.dart'; | |
1058 export 'a5.dart'; | |
1059 '''); | |
1060 updateFile(a6.source, 'library a6; import "a5.dart";'); | |
1061 results = []; | |
1062 rebuild(node, buildNoTransitiveChange); | |
1063 // a6 is not here, it's not reachable so we don't build it. | |
1064 expect(results, ['a2.dart', 'index3.html']); | |
1065 expectGraph( | |
1066 node, | |
1067 ''' | |
1068 index3.html | |
1069 |-- a2.dart | |
1070 | |-- a3.dart | |
1071 | |-- a4.dart | |
1072 | | |-- a10.dart | |
1073 | |-- a5.dart | |
1074 $_RUNTIME_GRAPH | |
1075 '''); | |
1076 }); | |
1077 | |
1078 test('convert a library to a part', () { | |
1079 var node = nodeOf('/index3.html'); | |
1080 var a2 = nodeOf('/a2.dart'); | |
1081 var a5 = nodeOf('/a5.dart'); | |
1082 rebuild(node, buildNoTransitiveChange); | |
1083 | |
1084 expectGraph( | |
1085 node, | |
1086 ''' | |
1087 index3.html | |
1088 |-- a2.dart | |
1089 | |-- a3.dart | |
1090 | |-- a4.dart | |
1091 | | |-- a10.dart | |
1092 | |-- a5.dart | |
1093 | |-- a6.dart (part) | |
1094 $_RUNTIME_GRAPH | |
1095 '''); | |
1096 | |
1097 updateFile( | |
1098 a2.source, | |
1099 ''' | |
1100 library a2; | |
1101 import 'a3.dart'; | |
1102 import 'a4.dart'; | |
1103 part 'a5.dart'; // make it a part | |
1104 part 'a6.dart'; | |
1105 '''); | |
1106 results = []; | |
1107 rebuild(node, buildNoTransitiveChange); | |
1108 expect(results, ['a2.dart', 'index3.html']); | |
1109 expectGraph( | |
1110 node, | |
1111 ''' | |
1112 index3.html | |
1113 |-- a2.dart | |
1114 | |-- a3.dart | |
1115 | |-- a4.dart | |
1116 | | |-- a10.dart | |
1117 | |-- a5.dart (part) | |
1118 | |-- a6.dart (part) | |
1119 $_RUNTIME_GRAPH | |
1120 '''); | |
1121 | |
1122 updateFile(a5.source, 'part of a2;'); | |
1123 results = []; | |
1124 rebuild(node, buildNoTransitiveChange); | |
1125 expect(results, ['a2.dart']); | |
1126 expectGraph( | |
1127 node, | |
1128 ''' | |
1129 index3.html | |
1130 |-- a2.dart | |
1131 | |-- a3.dart | |
1132 | |-- a4.dart | |
1133 | | |-- a10.dart | |
1134 | |-- a5.dart (part) | |
1135 | |-- a6.dart (part) | |
1136 $_RUNTIME_GRAPH | |
1137 '''); | |
1138 }); | |
1139 }); | |
1140 | |
1141 group('represented non-existing files', () { | |
1142 test('recognize locally change between existing and not-existing', () { | |
1143 var n = nodeOf('/foo.dart'); | |
1144 expect(n.source, isNotNull); | |
1145 expect(n.source.exists(), isFalse); | |
1146 var source = testUriResolver.resolveAbsolute(new Uri.file('/foo.dart')); | |
1147 expect(n.source, source); | |
1148 updateFile(source, "hi"); | |
1149 expect(n.source.exists(), isTrue); | |
1150 }); | |
1151 | |
1152 test('non-existing files are tracked in dependencies', () { | |
1153 var node = nodeOf('/foo.dart'); | |
1154 updateFile(node.source, "import 'bar.dart';"); | |
1155 rebuild(node, buildNoTransitiveChange); | |
1156 expect(node.allDeps.contains(nodeOf('/bar.dart')), isTrue); | |
1157 | |
1158 var source = nodeOf('/bar.dart').source; | |
1159 updateFile(source, "hi"); | |
1160 results = []; | |
1161 rebuild(node, buildWithTransitiveChange); | |
1162 expect(results, ['bar.dart', 'foo.dart']); | |
1163 }); | |
1164 }); | |
1165 | |
1166 group('null for non-existing files', () { | |
1167 setUp(() { | |
1168 context = createAnalysisContextWithSources(options.sourceOptions, | |
1169 fileResolvers: [testUriResolver]); | |
1170 graph = new SourceGraph(context, new LogReporter(context), options); | |
1171 }); | |
1172 | |
1173 test('recognize locally change between existing and not-existing', () { | |
1174 var n = nodeOf('/foo.dart'); | |
1175 expect(n.source.exists(), isFalse); | |
1176 var source = | |
1177 testResourceProvider.newFile('/foo.dart', 'hi').createSource(); | |
1178 expect( | |
1179 testUriResolver.resolveAbsolute(new Uri.file('/foo.dart')), source); | |
1180 expect(n.source, source); | |
1181 expect(n.source.exists(), isTrue); | |
1182 n.update(); | |
1183 expect(n.needsRebuild, isTrue); | |
1184 }); | |
1185 | |
1186 test('non-existing files are tracked in dependencies', () { | |
1187 testResourceProvider | |
1188 .newFile('/foo.dart', "import 'bar.dart';") | |
1189 .createSource(); | |
1190 var node = nodeOf('/foo.dart'); | |
1191 rebuild(node, buildNoTransitiveChange); | |
1192 expect(node.allDeps.length, 1); | |
1193 expect(node.allDeps.contains(nodeOf('/bar.dart')), isTrue); | |
1194 expect(nodeOf('/bar.dart').source.exists(), isFalse); | |
1195 | |
1196 testResourceProvider.newFile('/bar.dart', 'hi').createSource(); | |
1197 results = []; | |
1198 rebuild(node, buildWithTransitiveChange); | |
1199 expect(results, ['bar.dart', 'foo.dart']); | |
1200 }); | |
1201 }); | |
1202 }); | |
1203 } | |
1204 | |
1205 expectGraph(SourceNode node, String expectation) { | |
1206 expect(printReachable(node), equalsIgnoringWhitespace(expectation)); | |
1207 } | |
1208 | |
1209 nameFor(SourceNode node) => path.basename(node.uri.path); | |
1210 printReachable(SourceNode node) { | |
1211 var seen = new Set(); | |
1212 var sb = new StringBuffer(); | |
1213 helper(n, {indent: 0}) { | |
1214 if (indent > 0) { | |
1215 sb..write("| " * (indent - 1))..write("|-- "); | |
1216 } | |
1217 sb.write(nameFor(n)); | |
1218 if (seen.contains(n)) { | |
1219 sb.write('...\n'); | |
1220 return; | |
1221 } | |
1222 seen.add(n); | |
1223 sb | |
1224 ..write(' ') | |
1225 ..write(n.needsRebuild ? '[needs-rebuild] ' : '') | |
1226 ..write(n.structureChanged ? '[structure-changed] ' : ' ') | |
1227 ..write('\n'); | |
1228 n.depsWithoutParts.forEach((e) => helper(e, indent: indent + 1)); | |
1229 if (n is DartSourceNode) { | |
1230 n.parts.forEach((e) { | |
1231 sb | |
1232 ..write("| " * indent) | |
1233 ..write("|-- ") | |
1234 ..write(nameFor(e)) | |
1235 ..write(" (part) ") | |
1236 ..write(e.needsRebuild ? '[needs-rebuild] ' : '') | |
1237 ..write(e.structureChanged ? '[structure-changed] ' : ' ') | |
1238 ..write('\n'); | |
1239 }); | |
1240 } | |
1241 } | |
1242 helper(node); | |
1243 return sb.toString(); | |
1244 } | |
1245 | |
1246 final runtimeFilesWithoutPath = defaultRuntimeFiles | |
1247 .map((f) => f.replaceAll('dart/', '')) | |
1248 .toList(growable: false); | |
1249 final _RUNTIME_GRAPH = runtimeFilesWithoutPath.map((s) => '|-- $s').join('\n'); | |
1250 final _RUNTIME_GRAPH_REBUILD = | |
1251 runtimeFilesWithoutPath.map((s) => '|-- $s [needs-rebuild]').join('\n'); | |
OLD | NEW |