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 test.source.caching_pub_package_map_provider; | |
6 | |
7 import 'dart:convert'; | |
8 import 'dart:core'; | |
9 import 'dart:io' as io; | |
10 | |
11 import 'package:analysis_server/src/source/caching_pub_package_map_provider.dart
'; | |
12 import 'package:analyzer/file_system/file_system.dart'; | |
13 import 'package:analyzer/file_system/memory_file_system.dart'; | |
14 import 'package:analyzer/source/package_map_provider.dart'; | |
15 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
16 import 'package:analyzer/src/generated/engine.dart'; | |
17 import 'package:unittest/unittest.dart'; | |
18 | |
19 import '../utils.dart'; | |
20 | |
21 main() { | |
22 initializeTestEnvironment(); | |
23 | |
24 group('CachingPubPackageMapProvider', () { | |
25 MemoryResourceProvider resProvider; | |
26 _MockPubListRunner mockRunner; | |
27 bool writeFileException; | |
28 | |
29 Map result1 = { | |
30 'packages': {'foo': '/tmp/proj1/packages/foo'}, | |
31 'input_files': ['/tmp/proj1/pubspec.yaml'] | |
32 }; | |
33 | |
34 Map result1error = { | |
35 'input_files': ['/tmp/proj1/pubspec.lock'] | |
36 }; | |
37 | |
38 Map result2 = { | |
39 'packages': {'bar': '/tmp/proj2/packages/bar'}, | |
40 'input_files': ['/tmp/proj2/pubspec.yaml'] | |
41 }; | |
42 | |
43 Folder newProj(Map result) { | |
44 Map packages = result['packages']; | |
45 packages.forEach((String name, String path) { | |
46 resProvider.newFolder(path); | |
47 }); | |
48 List<String> inputFiles = result['input_files'] as List<String>; | |
49 for (String path in inputFiles) { | |
50 resProvider.newFile(path, ''); | |
51 } | |
52 Folder projectFolder = resProvider.getResource(inputFiles[0]).parent; | |
53 resProvider.newFile(projectFolder.path + '/pubspec.lock', ''); | |
54 return projectFolder; | |
55 } | |
56 | |
57 int mockWriteFile(File cacheFile, String content) { | |
58 if (writeFileException) { | |
59 throw 'simulated write failure: $cacheFile'; | |
60 } | |
61 if (!cacheFile.exists) { | |
62 resProvider.newFolder(cacheFile.parent.path); | |
63 resProvider.newFile(cacheFile.path, content); | |
64 } else { | |
65 resProvider.modifyFile(cacheFile.path, content); | |
66 } | |
67 Resource res = resProvider.getResource(cacheFile.path); | |
68 if (res is File) { | |
69 return res.createSource().modificationStamp; | |
70 } | |
71 throw 'expected file, but found $res'; | |
72 } | |
73 | |
74 CachingPubPackageMapProvider newPkgProvider() { | |
75 return new CachingPubPackageMapProvider( | |
76 resProvider, | |
77 new FolderBasedDartSdk( | |
78 resProvider, FolderBasedDartSdk.defaultSdkDirectory(resProvider)), | |
79 mockRunner.runPubList, | |
80 mockWriteFile); | |
81 } | |
82 | |
83 setUp(() { | |
84 resProvider = new MemoryResourceProvider(); | |
85 resProvider.newFolder('/tmp/proj/packages/foo'); | |
86 mockRunner = new _MockPubListRunner(); | |
87 writeFileException = false; | |
88 }); | |
89 | |
90 group('computePackageMap', () { | |
91 // Assert pub list called once and results are cached in memory | |
92 test('cache memory', () { | |
93 expect(mockRunner.runCount, 0); | |
94 | |
95 Folder folder1 = newProj(result1); | |
96 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
97 mockRunner.nextResult = JSON.encode(result1); | |
98 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
99 expect(mockRunner.runCount, 1); | |
100 _assertInfo(info, result1); | |
101 | |
102 info = pkgProvider.computePackageMap(folder1); | |
103 expect(mockRunner.runCount, 1); | |
104 _assertInfo(info, result1); | |
105 }); | |
106 | |
107 // Assert pub list called once and results are cached on disk | |
108 test('cache disk', () { | |
109 expect(mockRunner.runCount, 0); | |
110 | |
111 Folder folder1 = newProj(result1); | |
112 CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); | |
113 mockRunner.nextResult = JSON.encode(result1); | |
114 PackageMapInfo info = pkgProvider1.computePackageMap(folder1); | |
115 expect(mockRunner.runCount, 1); | |
116 _assertInfo(info, result1); | |
117 | |
118 CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); | |
119 info = pkgProvider2.computePackageMap(folder1); | |
120 expect(mockRunner.runCount, 1); | |
121 _assertInfo(info, result1); | |
122 }); | |
123 | |
124 // Assert pub list called even if cache file is corrupted | |
125 test('corrupt cache file', () { | |
126 expect(mockRunner.runCount, 0); | |
127 | |
128 Folder folder1 = newProj(result1); | |
129 CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); | |
130 resProvider.newFile(pkgProvider1.cacheFile.path, 'corrupt content'); | |
131 mockRunner.nextResult = JSON.encode(result1); | |
132 PackageMapInfo info = pkgProvider1.computePackageMap(folder1); | |
133 expect(mockRunner.runCount, 1); | |
134 _assertInfo(info, result1); | |
135 | |
136 CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); | |
137 info = pkgProvider2.computePackageMap(folder1); | |
138 expect(mockRunner.runCount, 1); | |
139 _assertInfo(info, result1); | |
140 }); | |
141 | |
142 // Assert gracefully continue even if write to file fails | |
143 test('failed write to cache file', () { | |
144 expect(mockRunner.runCount, 0); | |
145 | |
146 Folder folder1 = newProj(result1); | |
147 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
148 mockRunner.nextResult = JSON.encode(result1); | |
149 writeFileException = true; | |
150 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
151 expect(mockRunner.runCount, 1); | |
152 _assertInfo(info, result1); | |
153 | |
154 info = pkgProvider.computePackageMap(folder1); | |
155 expect(mockRunner.runCount, 1); | |
156 _assertInfo(info, result1); | |
157 }); | |
158 | |
159 // Assert modification in one shows up in the other | |
160 test('shared disk cache', () { | |
161 expect(mockRunner.runCount, 0); | |
162 | |
163 Folder folder1 = newProj(result1); | |
164 CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); | |
165 mockRunner.nextResult = JSON.encode(result1); | |
166 PackageMapInfo info = pkgProvider1.computePackageMap(folder1); | |
167 expect(mockRunner.runCount, 1); | |
168 _assertInfo(info, result1); | |
169 | |
170 Folder folder2 = newProj(result2); | |
171 CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); | |
172 mockRunner.nextResult = JSON.encode(result2); | |
173 info = pkgProvider2.computePackageMap(folder2); | |
174 expect(mockRunner.runCount, 2); | |
175 _assertInfo(info, result2); | |
176 | |
177 info = pkgProvider1.computePackageMap(folder2); | |
178 expect(mockRunner.runCount, 2); | |
179 _assertInfo(info, result2); | |
180 }); | |
181 | |
182 // Assert pub list called again if input file modified | |
183 test('input file changed', () { | |
184 expect(mockRunner.runCount, 0); | |
185 | |
186 Folder folder1 = newProj(result1); | |
187 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
188 mockRunner.nextResult = JSON.encode(result1); | |
189 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
190 expect(mockRunner.runCount, 1); | |
191 _assertInfo(info, result1); | |
192 | |
193 resProvider.modifyFile(info.dependencies.first, 'new content'); | |
194 mockRunner.nextResult = JSON.encode(result1); | |
195 info = pkgProvider.computePackageMap(folder1); | |
196 expect(mockRunner.runCount, 2); | |
197 _assertInfo(info, result1); | |
198 }); | |
199 | |
200 // Assert pub list called again if input file modified | |
201 // after reloading package provider cache from disk | |
202 test('input file changed 2', () { | |
203 expect(mockRunner.runCount, 0); | |
204 | |
205 Folder folder1 = newProj(result1); | |
206 CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); | |
207 mockRunner.nextResult = JSON.encode(result1); | |
208 PackageMapInfo info = pkgProvider1.computePackageMap(folder1); | |
209 expect(mockRunner.runCount, 1); | |
210 _assertInfo(info, result1); | |
211 | |
212 resProvider.modifyFile(info.dependencies.first, 'new content'); | |
213 mockRunner.nextResult = JSON.encode(result1); | |
214 CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); | |
215 info = pkgProvider2.computePackageMap(folder1); | |
216 expect(mockRunner.runCount, 2); | |
217 _assertInfo(info, result1); | |
218 }); | |
219 | |
220 // Assert pub list called again if input file deleted | |
221 test('input file deleted', () { | |
222 expect(mockRunner.runCount, 0); | |
223 | |
224 Folder folder1 = newProj(result1); | |
225 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
226 mockRunner.nextResult = JSON.encode(result1); | |
227 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
228 expect(mockRunner.runCount, 1); | |
229 _assertInfo(info, result1); | |
230 | |
231 resProvider.deleteFile(info.dependencies.first); | |
232 mockRunner.nextResult = JSON.encode(result1); | |
233 info = pkgProvider.computePackageMap(folder1); | |
234 expect(mockRunner.runCount, 2); | |
235 _assertInfo(info, result1); | |
236 }); | |
237 | |
238 // Assert pub list not called if folder does not exist | |
239 // and returns same cached result if folder restored as before | |
240 test('project removed then restored', () { | |
241 expect(mockRunner.runCount, 0); | |
242 | |
243 Folder folder1 = newProj(result1); | |
244 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
245 mockRunner.nextResult = JSON.encode(result1); | |
246 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
247 expect(mockRunner.runCount, 1); | |
248 _assertInfo(info, result1); | |
249 | |
250 _RestorePoint restorePoint = new _RestorePoint(resProvider, folder1); | |
251 resProvider.deleteFolder(folder1.path); | |
252 info = pkgProvider.computePackageMap(folder1); | |
253 expect(mockRunner.runCount, 1); | |
254 _assertError(info, result1error); | |
255 | |
256 restorePoint.restore(); | |
257 info = pkgProvider.computePackageMap(folder1); | |
258 expect(mockRunner.runCount, 1); | |
259 _assertInfo(info, result1); | |
260 }); | |
261 | |
262 // Assert pub list *is* run again | |
263 // if dependency has changed during execution | |
264 test('dependency changed during execution', () { | |
265 expect(mockRunner.runCount, 0); | |
266 | |
267 Folder folder1 = newProj(result1); | |
268 Resource pubspecFile = folder1.getChild('pubspec.yaml'); | |
269 expect(pubspecFile.exists, isTrue); | |
270 CachingPubPackageMapProvider pkgProvider = newPkgProvider(); | |
271 mockRunner.nextResultFunction = () { | |
272 resProvider.modifyFile(pubspecFile.path, 'new content'); | |
273 return JSON.encode(result1); | |
274 }; | |
275 mockRunner.nextResult = JSON.encode(result1); | |
276 PackageMapInfo info = pkgProvider.computePackageMap(folder1); | |
277 expect(mockRunner.runCount, 2); | |
278 _assertInfo(info, result1); | |
279 }); | |
280 }); | |
281 }); | |
282 } | |
283 | |
284 _assertError(PackageMapInfo info, Map expected) { | |
285 expect(info.packageMap, isNull); | |
286 List<String> expectedFiles = expected['input_files'] as List<String>; | |
287 expect(info.dependencies, hasLength(expectedFiles.length)); | |
288 for (String path in expectedFiles) { | |
289 expect(info.dependencies, contains(path)); | |
290 } | |
291 } | |
292 | |
293 _assertInfo(PackageMapInfo info, Map expected) { | |
294 Map<String, String> expectedPackages = | |
295 expected['packages'] as Map<String, String>; | |
296 expect(info.packageMap, hasLength(expectedPackages.length)); | |
297 for (String key in expectedPackages.keys) { | |
298 List<Folder> packageList = info.packageMap[key]; | |
299 expect(packageList, hasLength(1)); | |
300 expect(packageList[0].path, expectedPackages[key]); | |
301 } | |
302 List<String> expectedFiles = expected['input_files'] as List<String>; | |
303 expect(info.dependencies, hasLength(expectedFiles.length)); | |
304 for (String path in expectedFiles) { | |
305 expect(info.dependencies, contains(path)); | |
306 } | |
307 } | |
308 | |
309 typedef String MockResultFunction(); | |
310 | |
311 /** | |
312 * Mock for simulating and tracking execution of pub list | |
313 */ | |
314 class _MockPubListRunner { | |
315 int runCount = 0; | |
316 List nextResults = []; | |
317 | |
318 void set nextResult(String result) { | |
319 nextResults.add(result); | |
320 } | |
321 | |
322 void set nextResultFunction(MockResultFunction resultFunction) { | |
323 nextResults.add(resultFunction); | |
324 } | |
325 | |
326 io.ProcessResult runPubList(Folder folder) { | |
327 if (nextResults.isEmpty) { | |
328 throw 'missing nextResult'; | |
329 } | |
330 var result = nextResults.removeAt(0); | |
331 if (result is MockResultFunction) { | |
332 result = result(); | |
333 } | |
334 ++runCount; | |
335 return new _MockResult(result); | |
336 } | |
337 } | |
338 | |
339 class _MockResult implements io.ProcessResult { | |
340 String result; | |
341 | |
342 _MockResult(this.result); | |
343 | |
344 @override | |
345 int get exitCode => 0; | |
346 | |
347 // TODO: implement stdout | |
348 @override | |
349 get stdout => result; | |
350 | |
351 noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); | |
352 } | |
353 | |
354 /** | |
355 * An object containing information to restore the state of a deleted | |
356 * folder and its content. | |
357 */ | |
358 class _RestorePoint { | |
359 final MemoryResourceProvider provider; | |
360 final List<String> _folderPaths = <String>[]; | |
361 final List<String> _filePaths = <String>[]; | |
362 final List<TimestampedData> _fileContents = <TimestampedData>[]; | |
363 | |
364 /** | |
365 * Construct a new instance that captures the current state of the folder | |
366 * and all of its contained files and folders. | |
367 */ | |
368 _RestorePoint(this.provider, Folder folder) { | |
369 record(folder); | |
370 } | |
371 | |
372 /** | |
373 * Capture the current state of the folder | |
374 * and all of its contained files and folders. | |
375 */ | |
376 void record(Folder folder) { | |
377 _folderPaths.add(folder.path); | |
378 for (Resource child in folder.getChildren()) { | |
379 if (child is Folder) { | |
380 record(child); | |
381 } else if (child is File) { | |
382 _filePaths.add(child.path); | |
383 _fileContents.add(child.createSource().contents); | |
384 } else { | |
385 throw 'unknown resource: $child'; | |
386 } | |
387 } | |
388 } | |
389 | |
390 /** | |
391 * Restore the original files and folders. | |
392 */ | |
393 void restore() { | |
394 for (String path in _folderPaths) { | |
395 provider.newFolder(path); | |
396 } | |
397 int fileCount = _filePaths.length; | |
398 for (int fileIndex = 0; fileIndex < fileCount; ++fileIndex) { | |
399 String path = _filePaths[fileIndex]; | |
400 TimestampedData content = _fileContents[fileIndex]; | |
401 provider.newFile(path, content.data, content.modificationTime); | |
402 } | |
403 } | |
404 } | |
OLD | NEW |