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 |