Index: pkg/analysis_server/test/source/caching_put_package_map_provider_test.dart |
diff --git a/pkg/analysis_server/test/source/caching_put_package_map_provider_test.dart b/pkg/analysis_server/test/source/caching_put_package_map_provider_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c4e3210c598ba13376afb4e3969051884003d722 |
--- /dev/null |
+++ b/pkg/analysis_server/test/source/caching_put_package_map_provider_test.dart |
@@ -0,0 +1,403 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library test.source.caching_pub_package_map_provider; |
+ |
+import 'dart:convert'; |
+import 'dart:io' as io; |
+ |
+import 'package:analysis_server/src/source/caching_pub_package_map_provider.dart'; |
+import 'package:analyzer/file_system/file_system.dart'; |
+import 'package:analyzer/file_system/memory_file_system.dart'; |
+import 'package:analyzer/source/package_map_provider.dart'; |
+import 'package:analyzer/src/generated/engine.dart'; |
+import 'package:analyzer/src/generated/sdk_io.dart'; |
+import 'package:unittest/unittest.dart'; |
+ |
+main() { |
+ groupSep = ' | '; |
+ |
+ group('CachingPubPackageMapProvider', () { |
+ MemoryResourceProvider resProvider; |
+ _MockPubListRunner mockRunner; |
+ bool writeFileException; |
+ |
+ Map result1 = { |
+ 'packages': { |
+ 'foo': '/tmp/proj1/packages/foo' |
+ }, |
+ 'input_files': ['/tmp/proj1/pubspec.yaml'] |
+ }; |
+ |
+ Map result1error = { |
+ 'input_files': ['/tmp/proj1/pubspec.lock'] |
+ }; |
+ |
+ Map result2 = { |
+ 'packages': { |
+ 'bar': '/tmp/proj2/packages/bar' |
+ }, |
+ 'input_files': ['/tmp/proj2/pubspec.yaml'] |
+ }; |
+ |
+ Folder newProj(Map result) { |
+ Map packages = result['packages']; |
+ packages.forEach((String name, String path) { |
+ resProvider.newFolder(path); |
+ }); |
+ List<String> inputFiles = result['input_files']; |
+ for (String path in inputFiles) { |
+ resProvider.newFile(path, ''); |
+ } |
+ return resProvider.getResource(inputFiles[0]).parent; |
+ } |
+ |
+ int mockWriteFile(File cacheFile, String content) { |
+ if (writeFileException) { |
+ throw 'simulated write failure: $cacheFile'; |
+ } |
+ if (!cacheFile.exists) { |
+ resProvider.newFolder(cacheFile.parent.path); |
+ resProvider.newFile(cacheFile.path, content); |
+ } else { |
+ resProvider.modifyFile(cacheFile.path, content); |
+ } |
+ Resource res = resProvider.getResource(cacheFile.path); |
+ if (res is File) { |
+ return res.createSource().modificationStamp; |
+ } |
+ throw 'expected file, but found $res'; |
+ } |
+ |
+ CachingPubPackageMapProvider newPkgProvider() { |
+ return new CachingPubPackageMapProvider( |
+ resProvider, |
+ DirectoryBasedDartSdk.defaultSdk, |
+ mockRunner.runPubList, |
+ mockWriteFile); |
+ } |
+ |
+ setUp(() { |
+ resProvider = new MemoryResourceProvider(); |
+ resProvider.newFolder('/tmp/proj/packages/foo'); |
+ mockRunner = new _MockPubListRunner(); |
+ writeFileException = false; |
+ }); |
+ |
+ group('computePackageMap', () { |
+ |
+ // Assert pub list called once and results are cached in memory |
+ test('cache memory', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list called once and results are cached on disk |
+ test('cache disk', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider1.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); |
+ info = pkgProvider2.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list called even if cache file is corrupted |
+ test('corrupt cache file', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); |
+ resProvider.newFile(pkgProvider1.cacheFile.path, 'corrupt content'); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider1.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); |
+ info = pkgProvider2.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert gracefully continue even if write to file fails |
+ test('failed write to cache file', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ writeFileException = true; |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert modification in one shows up in the other |
+ test('shared disk cache', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider1.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ Folder folder2 = newProj(result2); |
+ CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result2); |
+ info = pkgProvider2.computePackageMap(folder2); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result2); |
+ |
+ info = pkgProvider1.computePackageMap(folder2); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result2); |
+ }); |
+ |
+ // Assert pub list called again if input file modified |
+ test('input file changed', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ resProvider.modifyFile(info.dependencies.first, 'new content'); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list called again if input file modified |
+ // after reloading package provider cache from disk |
+ test('input file changed 2', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider1 = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider1.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ resProvider.modifyFile(info.dependencies.first, 'new content'); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ CachingPubPackageMapProvider pkgProvider2 = newPkgProvider(); |
+ info = pkgProvider2.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list called again if input file deleted |
+ test('input file deleted', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ resProvider.deleteFile(info.dependencies.first); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list not called if folder does not exist |
+ // and returns same cached result if folder restored as before |
+ test('project removed then restored', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ |
+ _RestorePoint restorePoint = new _RestorePoint(resProvider, folder1); |
+ resProvider.deleteFolder(folder1.path); |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertError(info, result1error); |
+ |
+ restorePoint.restore(); |
+ info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 1); |
+ _assertInfo(info, result1); |
+ }); |
+ |
+ // Assert pub list *is* run again |
+ // if dependency has changed during execution |
+ test('dependency changed during execution', () { |
+ expect(mockRunner.runCount, 0); |
+ |
+ Folder folder1 = newProj(result1); |
+ Resource pubspecFile = folder1.getChild('pubspec.yaml'); |
+ expect(pubspecFile.exists, isTrue); |
+ CachingPubPackageMapProvider pkgProvider = newPkgProvider(); |
+ mockRunner.nextResultFunction = () { |
+ resProvider.modifyFile(pubspecFile.path, 'new content'); |
+ return JSON.encode(result1); |
+ }; |
+ mockRunner.nextResult = JSON.encode(result1); |
+ PackageMapInfo info = pkgProvider.computePackageMap(folder1); |
+ expect(mockRunner.runCount, 2); |
+ _assertInfo(info, result1); |
+ }); |
+ }); |
+ }); |
+} |
+ |
+_assertError(PackageMapInfo info, Map expected) { |
+ expect(info.packageMap, isNull); |
+ List<String> expectedFiles = expected['input_files']; |
+ expect(info.dependencies, hasLength(expectedFiles.length)); |
+ for (String path in expectedFiles) { |
+ expect(info.dependencies, contains(path)); |
+ } |
+} |
+ |
+_assertInfo(PackageMapInfo info, Map expected) { |
+ Map<String, String> expectedPackages = expected['packages']; |
+ expect(info.packageMap, hasLength(expectedPackages.length)); |
+ for (String key in expectedPackages.keys) { |
+ List<Folder> packageList = info.packageMap[key]; |
+ expect(packageList, hasLength(1)); |
+ expect(packageList[0].path, expectedPackages[key]); |
+ } |
+ List<String> expectedFiles = expected['input_files']; |
+ expect(info.dependencies, hasLength(expectedFiles.length)); |
+ for (String path in expectedFiles) { |
+ expect(info.dependencies, contains(path)); |
+ } |
+} |
+ |
+ |
+typedef String MockResultFunction(); |
+ |
+/** |
+ * Mock for simulating and tracking execution of pub list |
+ */ |
+class _MockPubListRunner { |
+ int runCount = 0; |
+ List nextResults = []; |
+ |
+ void set nextResult(String result) { |
+ nextResults.add(result); |
+ } |
+ |
+ void set nextResultFunction(MockResultFunction resultFunction) { |
+ nextResults.add(resultFunction); |
+ } |
+ |
+ io.ProcessResult runPubList(Folder folder) { |
+ if (nextResults.isEmpty) { |
+ throw 'missing nextResult'; |
+ } |
+ var result = nextResults.removeAt(0); |
+ if (result is MockResultFunction) { |
+ result = result(); |
+ } |
+ ++runCount; |
+ return new _MockResult(result); |
+ } |
+} |
+ |
+class _MockResult implements io.ProcessResult { |
+ String result; |
+ |
+ _MockResult(this.result); |
+ |
+ @override |
+ int get exitCode => 0; |
+ |
+ // TODO: implement stdout |
+ @override |
+ get stdout => result; |
+ |
+ noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
+} |
+ |
+/** |
+ * An object containing information to restore the state of a deleted |
+ * folder and its content. |
+ */ |
+class _RestorePoint { |
+ final MemoryResourceProvider provider; |
+ final List<String> _folderPaths = <String>[]; |
+ final List<String> _filePaths = <String>[]; |
+ final List<TimestampedData> _fileContents = <TimestampedData>[]; |
+ |
+ /** |
+ * Construct a new instance that captures the current state of the folder |
+ * and all of its contained files and folders. |
+ */ |
+ _RestorePoint(this.provider, Folder folder) { |
+ record(folder); |
+ } |
+ |
+ /** |
+ * Capture the current state of the folder |
+ * and all of its contained files and folders. |
+ */ |
+ void record(Folder folder) { |
+ _folderPaths.add(folder.path); |
+ for (Resource child in folder.getChildren()) { |
+ if (child is Folder) { |
+ record(child); |
+ } else if (child is File) { |
+ _filePaths.add(child.path); |
+ _fileContents.add(child.createSource().contents); |
+ } else { |
+ throw 'unknown resource: $child'; |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Restore the original files and folders. |
+ */ |
+ void restore() { |
+ for (String path in _folderPaths) { |
+ provider.newFolder(path); |
+ } |
+ int fileCount = _filePaths.length; |
+ for (int fileIndex = 0; fileIndex < fileCount; ++fileIndex) { |
+ String path = _filePaths[fileIndex]; |
+ TimestampedData content = _fileContents[fileIndex]; |
+ provider.newFile(path, content.data, content.modificationTime); |
+ } |
+ } |
+} |