Chromium Code Reviews| Index: pkg/analyzer/test/src/summary/summary_test.dart |
| diff --git a/pkg/analyzer/test/src/summary/summary_test.dart b/pkg/analyzer/test/src/summary/summary_test.dart |
| index f73451bc8e712c14047f289c6200afbe45fbb4f2..8250ce50d927e9d7db3cb3f233890433ffa5ecd6 100644 |
| --- a/pkg/analyzer/test/src/summary/summary_test.dart |
| +++ b/pkg/analyzer/test/src/summary/summary_test.dart |
| @@ -15,6 +15,7 @@ import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/summary/base.dart'; |
| import 'package:analyzer/src/summary/format.dart'; |
| +import 'package:analyzer/src/summary/prelink.dart'; |
| import 'package:analyzer/src/summary/public_namespace_computer.dart' |
| as public_namespace; |
| import 'package:analyzer/src/summary/summarize_elements.dart' |
| @@ -27,6 +28,161 @@ import '../../reflective_tests.dart'; |
| main() { |
| groupSep = ' | '; |
| runReflectiveTests(SummarizeElementsTest); |
| + runReflectiveTests(PrelinkerTest); |
| +} |
| + |
| +/** |
| + * Convert a summary object (or a portion of one) into a canonical form that |
| + * can be easily compared using [expect]. If [orderByName] is true, and the |
| + * object is a [List], it is sorted by the `name` field of its elements. |
| + */ |
| +Object canonicalize(Object obj, {bool orderByName: false}) { |
| + if (obj is SummaryClass) { |
| + Map<String, Object> result = <String, Object>{}; |
| + obj.toMap().forEach((String key, Object value) { |
| + bool orderByName = false; |
| + if (obj is UnlinkedPublicNamespace && key == 'names') { |
| + orderByName = true; |
| + } |
| + result[key] = canonicalize(value, orderByName: orderByName); |
| + }); |
| + return result; |
| + } else if (obj is List) { |
| + List<Object> result = <Object>[]; |
| + for (Object item in obj) { |
| + result.add(canonicalize(item)); |
| + } |
| + if (orderByName) { |
| + result.sort((Object a, Object b) { |
| + if (a is Map && b is Map) { |
| + return Comparable.compare(a['name'], b['name']); |
| + } else { |
| + return 0; |
| + } |
| + }); |
| + } |
| + return result; |
| + } else if (obj is String || obj is num || obj is bool) { |
| + return obj; |
| + } else { |
| + return obj.toString(); |
| + } |
| +} |
| + |
| +UnlinkedPublicNamespace computePublicNamespaceFromText( |
| + String text, Source source) { |
| + CharacterReader reader = new CharSequenceReader(text); |
| + Scanner scanner = |
| + new Scanner(source, reader, AnalysisErrorListener.NULL_LISTENER); |
| + Parser parser = new Parser(source, AnalysisErrorListener.NULL_LISTENER); |
| + parser.parseGenericMethods = true; |
| + CompilationUnit unit = parser.parseCompilationUnit(scanner.tokenize()); |
| + UnlinkedPublicNamespace namespace = new UnlinkedPublicNamespace.fromBuffer( |
| + public_namespace |
| + .computePublicNamespace(new BuilderContext(), unit) |
| + .toBuffer()); |
| + return namespace; |
| +} |
| + |
| +/** |
| + * Override of [SummaryTest] which verifies the correctness of the prelinker by |
| + * creating summaries from the element model, discarding their prelinked |
| + * information, and then recreating it using the prelinker. |
| + */ |
| +@reflectiveTest |
| +class PrelinkerTest extends SummarizeElementsTest { |
| + /** |
| + * The public namespaces of the sdk are computed once so that we don't bog |
| + * down the test. Structured as a map from absolute URI to the corresponding |
| + * public namespace. |
| + * |
| + * Note: should an exception occur during computation of this variable, it |
| + * will silently be set to null to allow other tests to run. |
| + */ |
| + static final Map<String, UnlinkedPublicNamespace> sdkPublicNamespace = () { |
| + try { |
| + AnalysisContext analysisContext = |
| + AnalysisContextFactory.contextWithCore(); |
| + Map<String, UnlinkedPublicNamespace> uriToNamespace = |
| + <String, UnlinkedPublicNamespace>{}; |
| + List<LibraryElement> libraries = [ |
| + analysisContext.typeProvider.objectType.element.library, |
| + analysisContext.typeProvider.futureType.element.library |
| + ]; |
| + for (LibraryElement library in libraries) { |
| + summarize_elements.LibrarySerializationResult serializedLibrary = |
| + summarize_elements.serializeLibrary( |
| + new BuilderContext(), library, analysisContext.typeProvider); |
| + for (int i = 0; i < serializedLibrary.unlinkedUnits.length; i++) { |
| + uriToNamespace[ |
| + serializedLibrary.unitUris[i]] = new UnlinkedUnit.fromBuffer( |
| + serializedLibrary.unlinkedUnits[i].toBuffer()).publicNamespace; |
| + } |
| + } |
| + return uriToNamespace; |
| + } catch (_) { |
| + return null; |
| + } |
| + }(); |
| + |
| + final Map<String, UnlinkedPublicNamespace> uriToPublicNamespace = |
| + <String, UnlinkedPublicNamespace>{}; |
| + |
| + @override |
| + bool get expectAbsoluteUrisInDependencies => false; |
| + |
| + @override |
| + Source addNamedSource(String filePath, String contents) { |
| + Source source = super.addNamedSource(filePath, contents); |
| + uriToPublicNamespace[absUri(filePath)] = |
| + computePublicNamespaceFromText(contents, source); |
| + return source; |
| + } |
| + |
| + String resolveAbsoluteUri(LibraryElement library, String relativeUri) { |
|
scheglov
2016/01/10 05:15:12
resolveToAbsoluteUri would be better maybe?
Paul Berry
2016/01/10 22:15:17
Done.
|
| + Source resolvedSource = |
| + analysisContext.sourceFactory.resolveUri(library.source, relativeUri); |
| + if (resolvedSource == null) { |
| + fail('Failed to resolve relative uri "$relativeUri"'); |
| + } |
| + String absoluteUri = resolvedSource.uri.toString(); |
| + return absoluteUri; |
|
scheglov
2016/01/10 05:15:12
Could be inlined.
Paul Berry
2016/01/10 22:15:17
Done.
|
| + } |
| + |
| + @override |
| + void serializeLibraryElement(LibraryElement library) { |
| + super.serializeLibraryElement(library); |
| + Map<String, UnlinkedUnit> uriToUnit = <String, UnlinkedUnit>{}; |
| + expect(unlinkedUnits.length, unitUris.length); |
| + for (int i = 1; i < unlinkedUnits.length; i++) { |
| + uriToUnit[unitUris[i]] = unlinkedUnits[i]; |
| + } |
| + UnlinkedUnit getPart(String relativeUri) { |
| + String absoluteUri = resolveAbsoluteUri(library, relativeUri); |
| + UnlinkedUnit unit = uriToUnit[absoluteUri]; |
| + if (unit == null) { |
| + fail('Prelinker unexpectedly requested unit for "$relativeUri"' |
| + ' (resolves to "$absoluteUri").'); |
| + } |
| + return unit; |
| + } |
| + UnlinkedPublicNamespace getImport(String relativeUri) { |
| + String absoluteUri = resolveAbsoluteUri(library, relativeUri); |
| + UnlinkedPublicNamespace namespace = sdkPublicNamespace[absoluteUri]; |
| + if (namespace == null) { |
| + namespace = uriToPublicNamespace[absoluteUri]; |
| + } |
| + if (namespace == null && !allowMissingFiles) { |
| + fail('Prelinker unexpectedly requested namespace for "$relativeUri"' |
| + ' (resolves to "$absoluteUri").' |
| + ' Namespaces available: ${uriToPublicNamespace.keys}'); |
| + } |
| + return namespace; |
| + } |
| + prelinked = new PrelinkedLibrary.fromBuffer( |
| + prelink(builderContext, unlinkedUnits[0], getPart, getImport) |
| + .toBuffer()); |
| + } |
| } |
| /** |
| @@ -45,41 +201,8 @@ class SummarizeElementsTest extends ResolverTestCase with SummaryTest { |
| @override |
| bool get checkAstDerivedData => false; |
| - /** |
| - * Convert a summary object (or a portion of one) into a canonical form that |
| - * can be easily compared using [expect]. If [orderByName] is true, and the |
| - * object is a [List], it is sorted by the `name` field of its elements. |
| - */ |
| - Object canonicalize(Object obj, {bool orderByName: false}) { |
| - if (obj is SummaryClass) { |
| - Map<String, Object> result = <String, Object>{}; |
| - obj.toMap().forEach((String key, Object value) { |
| - bool orderByName = false; |
| - if (obj is UnlinkedPublicNamespace && key == 'names') { |
| - orderByName = true; |
| - } |
| - result[key] = canonicalize(value, orderByName: orderByName); |
| - }); |
| - return result; |
| - } else if (obj is List) { |
| - List<Object> result = <Object>[]; |
| - for (Object item in obj) { |
| - result.add(canonicalize(item)); |
| - } |
| - if (orderByName) { |
| - result.sort((Object a, Object b) { |
| - if (a is Map && b is Map) { |
| - return Comparable.compare(a['name'], b['name']); |
| - } else { |
| - return 0; |
| - } |
| - }); |
| - } |
| - return result; |
| - } else { |
| - return obj; |
| - } |
| - } |
| + @override |
| + bool get expectAbsoluteUrisInDependencies => true; |
| /** |
| * Serialize the library containing the given class [element], then |
| @@ -149,16 +272,8 @@ class SummarizeElementsTest extends ResolverTestCase with SummaryTest { |
| for (int i = 0; i < unlinkedUnits.length; i++) { |
| Source source = analysisContext.sourceFactory.forUri(unitUris[i]); |
| String text = analysisContext.getContents(source).data; |
| - CharacterReader reader = new CharSequenceReader(text); |
| - Scanner scanner = |
| - new Scanner(source, reader, AnalysisErrorListener.NULL_LISTENER); |
| - Parser parser = new Parser(source, AnalysisErrorListener.NULL_LISTENER); |
| - parser.parseGenericMethods = true; |
| - CompilationUnit unit = parser.parseCompilationUnit(scanner.tokenize()); |
| UnlinkedPublicNamespace namespace = |
| - new UnlinkedPublicNamespace.fromBuffer(public_namespace |
| - .computePublicNamespace(builderContext, unit) |
| - .toBuffer()); |
| + computePublicNamespaceFromText(text, source); |
| expect(canonicalize(namespace), |
| canonicalize(unlinkedUnits[i].publicNamespace), |
| reason: 'publicNamespace(${unitUris[i]})'); |
| @@ -186,6 +301,12 @@ abstract class SummaryTest { |
| List<UnlinkedUnit> unlinkedUnits; |
| /** |
| + * A test will set this to `true` if it contains `import`, `export`, or |
| + * `part` declarations that deliberately refer to non-existent files. |
| + */ |
| + bool allowMissingFiles = false; |
| + |
| + /** |
| * `true` if the summary was created directly from the AST (and hence |
| * contains information that is not obtainable from the element model alone). |
| * TODO(paulberry): modify the element model so that it contains all the data |
| @@ -199,6 +320,13 @@ abstract class SummaryTest { |
| PrelinkedUnit get definingUnit => prelinked.units[0]; |
| /** |
| + * `true` if the prelinked portion of the summary is expected to contain |
| + * absolute URIs. This happens because the element model doesn't (yet) store |
| + * enough information to recover relative URIs, TODO(paulberry): fix this. |
| + */ |
| + bool get expectAbsoluteUrisInDependencies; |
| + |
| + /** |
| * Convert [path] to a suitably formatted absolute path URI for the current |
| * platform. |
| */ |
| @@ -210,14 +338,14 @@ abstract class SummaryTest { |
| * Add the given source file so that it may be referenced by the file under |
| * test. |
| */ |
| - addNamedSource(String filePath, String contents); |
| + Source addNamedSource(String filePath, String contents); |
| /** |
| * Verify that the [dependency]th element of the dependency table represents |
| * a file reachable via the given [absoluteUri] and [relativeUri]. |
| */ |
| void checkDependency(int dependency, String absoluteUri, String relativeUri) { |
| - if (!checkAstDerivedData) { |
| + if (expectAbsoluteUrisInDependencies) { |
| // The element model doesn't (yet) store enough information to recover |
| // relative URIs, so we have to use the absolute URI. |
| // TODO(paulberry): fix this. |
| @@ -262,18 +390,20 @@ abstract class SummaryTest { |
| * via the given [absoluteUri] and [relativeUri]. |
| */ |
| void checkHasDependency(String absoluteUri, String relativeUri) { |
| - if (!checkAstDerivedData) { |
| + if (expectAbsoluteUrisInDependencies) { |
| // The element model doesn't (yet) store enough information to recover |
| // relative URIs, so we have to use the absolute URI. |
| // TODO(paulberry): fix this. |
| relativeUri = absoluteUri; |
| } |
| + List<String> found = <String>[]; |
| for (PrelinkedDependency dep in prelinked.dependencies) { |
| if (dep.uri == relativeUri) { |
| return; |
| } |
| + found.add(dep.uri); |
| } |
| - fail('Did not find dependency $absoluteUri'); |
| + fail('Did not find dependency $relativeUri. Found: $found'); |
| } |
| /** |
| @@ -281,7 +411,7 @@ abstract class SummaryTest { |
| * reachable via the given [absoluteUri] and [relativeUri]. |
| */ |
| void checkLacksDependency(String absoluteUri, String relativeUri) { |
| - if (!checkAstDerivedData) { |
| + if (expectAbsoluteUrisInDependencies) { |
| // The element model doesn't (yet) store enough information to recover |
| // relative URIs, so we have to use the absolute URI. |
| // TODO(paulberry): fix this. |
| @@ -363,7 +493,11 @@ abstract class SummaryTest { |
| if (!allowTypeParameters) { |
| expect(typeRef.typeArguments, isEmpty); |
| } |
| - if (expectedName == null) { |
| + if (expectedKind == PrelinkedReferenceKind.unresolved) { |
| + // summarize_elements.dart isn't yet able to record the name of |
| + // unresolved references. TODO(paulberry): fix this. |
| + expect(reference.name, '*unresolved*'); |
| + } else if (expectedName == null) { |
| expect(reference.name, isEmpty); |
| } else { |
| expect(reference.name, expectedName); |
| @@ -410,6 +544,7 @@ enum E { |
| // the element model, so we can't pass this test. |
| // Unresolved imports are included since this is necessary for proper |
| // dependency tracking. |
| + allowMissingFiles = true; |
| serializeLibraryText('import "foo.dart";', allowErrors: true); |
| // Second import is the implicit import of dart:core |
| expect(unlinkedUnits[0].imports, hasLength(2)); |
| @@ -421,6 +556,7 @@ enum E { |
| // TODO(paulberry): this test currently fails because there is not enough |
| // information in the element model to figure out that the unresolved |
| // reference `p.C` uses the prefix `p`. |
| + allowMissingFiles = true; |
| UnlinkedTypeRef typeRef = serializeTypeText('p.C', |
| otherDeclarations: 'import "foo.dart" as p;', allowErrors: true); |
| checkUnresolvedTypeRef(typeRef, 'p', 'C'); |
| @@ -635,8 +771,7 @@ b.C c4;'''); |
| UnlinkedTypeRef serializeTypeText(String text, |
| {String otherDeclarations: '', bool allowErrors: false}) { |
| return serializeVariableText('$otherDeclarations\n$text v;', |
| - allowErrors: allowErrors) |
| - .type; |
| + allowErrors: allowErrors).type; |
| } |
| /** |
| @@ -1315,17 +1450,17 @@ class C { |
| checkHasDependency(absUri('/a/a.dart'), 'a/a.dart'); |
| // The main test library depends on b.dart, because names defined in |
| // b.dart are exported by a.dart. |
| - checkHasDependency(absUri('/a/b/b.dart'), '/a/b/b.dart'); |
| + checkHasDependency(absUri('/a/b/b.dart'), absUri('/a/b/b.dart')); |
| } |
| test_dependencies_import_to_export_in_subdirs_absolute_import() { |
| addNamedSource('/a/a.dart', 'library a; export "b/b.dart"; class A {}'); |
| addNamedSource('/a/b/b.dart', 'library b;'); |
| serializeLibraryText('import "${absUri('/a/a.dart')}"; A a;'); |
| - checkHasDependency(absUri('/a/a.dart'), '/a/a.dart'); |
| + checkHasDependency(absUri('/a/a.dart'), absUri('/a/a.dart')); |
| // The main test library depends on b.dart, because names defined in |
| // b.dart are exported by a.dart. |
| - checkHasDependency(absUri('/a/b/b.dart'), '/a/b/b.dart'); |
| + checkHasDependency(absUri('/a/b/b.dart'), absUri('/a/b/b.dart')); |
| } |
| test_dependencies_import_to_export_in_subdirs_relative() { |
| @@ -1662,8 +1797,7 @@ enum E { v }'''; |
| test_executable_operator_index_set() { |
| UnlinkedExecutable executable = serializeClassText( |
| - 'class C { void operator[]=(int i, bool v) => null; }') |
| - .executables[0]; |
| + 'class C { void operator[]=(int i, bool v) => null; }').executables[0]; |
| expect(executable.kind, UnlinkedExecutableKind.functionOrMethod); |
| expect(executable.name, '[]='); |
| expect(executable.hasImplicitReturnType, false); |
| @@ -2102,6 +2236,7 @@ get f => null;'''; |
| test_import_of_file_with_missing_part() { |
| // Other references in foo.dart should be resolved even though foo.dart's |
| // part declaration for bar.dart refers to a non-existent file. |
| + allowMissingFiles = true; |
| addNamedSource('/foo.dart', 'part "bar.dart"; class C {}'); |
| serializeLibraryText('import "foo.dart"; C x;'); |
| checkTypeRef(findVariable('x').type, absUri('/foo.dart'), 'foo.dart', 'C'); |
| @@ -2110,6 +2245,7 @@ get f => null;'''; |
| test_import_of_missing_export() { |
| // Other references in foo.dart should be resolved even though foo.dart's |
| // re-export of bar.dart refers to a non-existent file. |
| + allowMissingFiles = true; |
| addNamedSource('/foo.dart', 'export "bar.dart"; class C {}'); |
| serializeLibraryText('import "foo.dart"; C x;'); |
| checkTypeRef(findVariable('x').type, absUri('/foo.dart'), 'foo.dart', 'C'); |
| @@ -2154,6 +2290,29 @@ get f => null;'''; |
| expectedPrefix: 'a', numTypeParameters: 1); |
| } |
| + test_import_prefixes_take_precedence_over_imported_names() { |
| + addNamedSource('/a.dart', 'class b {} class A'); |
| + addNamedSource('/b.dart', 'class Cls {}'); |
| + addNamedSource('/c.dart', 'class Cls {}'); |
| + addNamedSource('/d.dart', 'class c {} class D'); |
| + serializeLibraryText(''' |
| +import 'a.dart'; |
| +import 'b.dart' as b; |
| +import 'c.dart' as c; |
| +import 'd.dart'; |
| +A aCls; |
| +b.Cls bCls; |
| +c.Cls cCls; |
| +D dCls; |
| +'''); |
| + checkTypeRef(findVariable('aCls').type, absUri('/a.dart'), 'a.dart', 'A'); |
| + checkTypeRef(findVariable('bCls').type, absUri('/b.dart'), 'b.dart', 'Cls', |
| + expectedPrefix: 'b'); |
| + checkTypeRef(findVariable('cCls').type, absUri('/c.dart'), 'c.dart', 'Cls', |
| + expectedPrefix: 'c'); |
| + checkTypeRef(findVariable('dCls').type, absUri('/d.dart'), 'd.dart', 'D'); |
| + } |
| + |
| test_import_reference() { |
| UnlinkedVariable variable = |
| serializeVariableText('import "dart:async"; Future v;'); |
| @@ -2189,6 +2348,22 @@ a.Stream s; |
| expectedPrefix: 'a', numTypeParameters: 1); |
| } |
| + test_import_reference_merged_prefixed_separate_libraries() { |
| + addNamedSource('/a.dart', 'class A {}'); |
| + addNamedSource('/b.dart', 'class B {}'); |
| + serializeLibraryText(''' |
| +import 'a.dart' as p; |
| +import 'b.dart' as p; |
| + |
| +p.A a; |
| +p.B b; |
| +'''); |
| + checkTypeRef(findVariable('a').type, absUri('/a.dart'), 'a.dart', 'A', |
| + expectedPrefix: 'p'); |
| + checkTypeRef(findVariable('b').type, absUri('/b.dart'), 'b.dart', 'B', |
| + expectedPrefix: 'p'); |
| + } |
| + |
| test_import_show_order() { |
| String libraryText = |
| 'import "dart:async" show Future, Stream; Future x; Stream y;'; |
| @@ -2247,6 +2422,17 @@ library foo;'''; |
| expect(unlinkedUnits[0].libraryNameLength, 0); |
| } |
| + test_local_names_take_precedence_over_imported_names() { |
| + addNamedSource('/a.dart', 'class C {} class D {}'); |
| + serializeLibraryText(''' |
| +import 'a.dart'; |
| +class C {} |
| +C c; |
| +D d;'''); |
| + checkTypeRef(findVariable('c').type, null, null, 'C'); |
| + checkTypeRef(findVariable('d').type, absUri('/a.dart'), 'a.dart', 'D'); |
| + } |
| + |
| test_method_documented() { |
| String text = ''' |
| class C { |