| // Copyright (c) 2017, 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. |
| |
| import 'package:_fe_analyzer_shared/src/util/libraries_specification.dart'; |
| import 'package:test/test.dart'; |
| |
| bool Function(dynamic) checkException(String expectedMessage) { |
| return (exception) { |
| print(exception); |
| if (exception is LibrariesSpecificationException) { |
| expect(exception.error, expectedMessage); |
| return true; |
| } |
| return false; |
| }; |
| } |
| |
| void main() { |
| String notFoundMessage = 'File not found'; |
| |
| Uri specUri = Uri.parse('org-dartlang-test:///f.json'); |
| |
| Future<String> Function(Uri) read(Map<Uri, String> map) { |
| return (Uri uri) async { |
| String? jsonString = map[uri]; |
| if (jsonString == null) { |
| throw notFoundMessage; |
| } |
| return jsonString; |
| }; |
| } |
| |
| group('parse', () { |
| test('top-level must be a map', () async { |
| var jsonString = '[]'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageTopLevelIsNotAMap(specUri)))); |
| jsonString = '""'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageTopLevelIsNotAMap(specUri)))); |
| }); |
| |
| test('target entry must be a map', () async { |
| var jsonString = '{"vm" : []}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageTargetIsNotAMap('vm', specUri)))); |
| jsonString = '{"vm" : ""}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageTargetIsNotAMap('vm', specUri)))); |
| }); |
| |
| test('library entry must exist', () async { |
| var jsonString = '{"vm" : {}}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageTargetLibrariesMissing('vm', specUri)))); |
| }); |
| |
| test('library entry must be a map', () async { |
| var jsonString = '{"vm" : {"libraries": []}}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageLibrariesEntryIsNotAMap('vm', specUri)))); |
| }); |
| |
| test('library data must be a map', () async { |
| var jsonString = '{"vm" : {"libraries": { "foo": [] }}}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageLibraryDataIsNotAMap('foo', 'vm', specUri)))); |
| }); |
| |
| test('library uri must be supplied', () async { |
| var jsonString = '{"vm" : {"libraries": {"core": {}}}}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageLibraryUriMissing('core', 'vm', specUri)))); |
| }); |
| |
| test('uri must be a string', () async { |
| var jsonString = '{"vm" : {"libraries": {"core": {"uri": 3}}}}'; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageLibraryUriIsNotAString(3, 'core', 'vm', specUri)))); |
| }); |
| |
| test('patches must be a string or list of string', () async { |
| var jsonString = ''' |
| { |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart", |
| "patches": 3 |
| } |
| } |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messagePatchesMustBeListOrString('c')))); |
| |
| jsonString = ''' |
| { |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart", |
| "patches": "a.dart" |
| } |
| } |
| } |
| } |
| '''; |
| var spec = await LibrariesSpecification.load( |
| specUri, read({specUri: jsonString})); |
| expect(spec.specificationFor("none").libraryInfoFor("c")!.patches.first, |
| Uri.parse('org-dartlang-test:///a.dart')); |
| |
| jsonString = ''' |
| { |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart", |
| "patches": ["a.dart"] |
| } |
| } |
| } |
| } |
| '''; |
| spec = await LibrariesSpecification.load( |
| specUri, read({specUri: jsonString})); |
| expect(spec.specificationFor("none").libraryInfoFor("c")!.patches.first, |
| Uri.parse('org-dartlang-test:///a.dart')); |
| }); |
| |
| test('patches are optional in the format', () async { |
| var jsonString = ''' |
| { "none": { "libraries": {"c" : { "uri": "c/main.dart" }}}} |
| '''; |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| expect(spec, isNotNull); |
| expect( |
| spec.specificationFor('none').libraryInfoFor('c')!.patches, isEmpty); |
| }); |
| |
| test('library paths are resolved from spec uri', () async { |
| var jsonString = ''' |
| { "none": { "libraries": {"c" : { "uri": "c/main.dart" }}}} |
| '''; |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| expect(spec.specificationFor('none').libraryInfoFor('c')!.uri, |
| Uri.parse('org-dartlang-test:///one/two/c/main.dart')); |
| }); |
| |
| test('patches paths are resolved from spec uri', () async { |
| var jsonString = ''' |
| { |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart", |
| "patches": [ |
| "../a/p1.dart", |
| "../a/p2.dart" |
| ] |
| } |
| } |
| } |
| } |
| '''; |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| expect(spec.specificationFor('none').libraryInfoFor('c')!.patches[0], |
| Uri.parse('org-dartlang-test:///one/a/p1.dart')); |
| expect(spec.specificationFor('none').libraryInfoFor('c')!.patches[1], |
| Uri.parse('org-dartlang-test:///one/a/p2.dart')); |
| }); |
| |
| test('multiple targets are supported', () async { |
| var jsonString = ''' |
| { |
| "vm": { |
| "libraries": { |
| "foo" : { |
| "uri": "a/main.dart", |
| "patches": [ |
| "a/p1.dart", |
| "a/p2.dart" |
| ] |
| }, |
| "bar" : { |
| "uri": "b/main.dart", |
| "patches": [ |
| "b/p3.dart" |
| ] |
| } |
| } |
| }, |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart" |
| } |
| } |
| } |
| } |
| '''; |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| |
| expect(spec.specificationFor('vm').libraryInfoFor('foo')!.uri, |
| Uri.parse('org-dartlang-test:///one/two/a/main.dart')); |
| expect(spec.specificationFor('vm').libraryInfoFor('bar')!.uri, |
| Uri.parse('org-dartlang-test:///one/two/b/main.dart')); |
| expect(spec.specificationFor('none').libraryInfoFor('c')!.uri, |
| Uri.parse('org-dartlang-test:///one/two/c/main.dart')); |
| }); |
| |
| test('supported entry must be bool', () async { |
| var jsonString = ''' |
| { |
| "vm": |
| { |
| "libraries": |
| { |
| "core": { |
| "uri": "main.dart", |
| "supported": 3 |
| } |
| } |
| } |
| }'''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageSupportedIsNotABool(3)))); |
| }); |
| |
| test('supported entry is copied correctly when parsing', () async { |
| var jsonString = ''' |
| { |
| "vm": { |
| "libraries": { |
| "foo" : { |
| "uri": "a/main.dart", |
| "patches": [ |
| "a/p1.dart", |
| "a/p2.dart" |
| ], |
| "supported": false |
| |
| }, |
| "bar" : { |
| "uri": "b/main.dart", |
| "patches": [ |
| "b/p3.dart" |
| ], |
| "supported": true |
| }, |
| "baz" : { |
| "uri": "b/main.dart", |
| "patches": [ |
| "b/p3.dart" |
| ] |
| } |
| } |
| } |
| } |
| '''; |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| expect(spec.specificationFor('vm').libraryInfoFor('foo')!.isSupported, |
| false); |
| expect( |
| spec.specificationFor('vm').libraryInfoFor('bar')!.isSupported, true); |
| expect( |
| spec.specificationFor('vm').libraryInfoFor('baz')!.isSupported, true); |
| }); |
| }); |
| |
| group('nested', () { |
| test('include must be a list', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": "", |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageIncludeIsNotAList('target', specUri)))); |
| }); |
| |
| test('include entry must be a map', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [""], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageIncludeEntryIsNotAMap('target', specUri)))); |
| }); |
| |
| test('include entry must have a target string', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageIncludeTargetMissing('target', specUri)))); |
| |
| jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "dummy"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageIncludeTargetMissing('target', specUri)))); |
| |
| jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": 0, "target": 0}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageIncludeTargetIsNotAString('target', specUri)))); |
| }); |
| |
| test('include entry path must be a string', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": 0, "target": "dummy"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageIncludePathIsNotAString('target', specUri)))); |
| |
| jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "dummy", "target": 0}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageIncludeTargetIsNotAString('target', specUri)))); |
| }); |
| |
| test('include entry path scheme can only be file', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "http://foo.dart", "target": "none"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageUnsupportedUriScheme("http://foo.dart", specUri)))); |
| }); |
| |
| test('include entry must be a existing path and target', () async { |
| var otherFile = 'g.json'; |
| var otherUri = specUri.resolve(otherFile); |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "$otherFile", "target": "none"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException( |
| messageIncludePathCouldNotBeRead(otherUri, notFoundMessage)))); |
| |
| var otherJsonString = ''' |
| { |
| "target": { |
| "libraries": {} |
| } |
| }'''; |
| expect( |
| () => LibrariesSpecification.load( |
| specUri, read({specUri: jsonString, otherUri: otherJsonString})), |
| throwsA(checkException(messageMissingTarget('none', otherUri)))); |
| }); |
| |
| test('internal include target must exist', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{"target": "none"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageMissingTarget('none', specUri)))); |
| }); |
| |
| test('include entry cannot be cyclic', () async { |
| var thisFile = 'f.json'; |
| var thisUri = specUri.resolve(thisFile); |
| var otherFile = 'g.json'; |
| var otherUri = thisUri.resolve(otherFile); |
| var thisJsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "$thisFile", "target": "target"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => LibrariesSpecification.load( |
| thisUri, read({thisUri: thisJsonString})), |
| throwsA(checkException(messageCyclicSpec(thisUri)))); |
| |
| thisJsonString = ''' |
| { |
| "target": { |
| "include": [{"path": "$otherFile", "target": "none"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| var otherJsonString = ''' |
| { |
| "none": { |
| "include": [{"path": "$thisFile", "target": "target"}], |
| "libraries": {} |
| } |
| }'''; |
| expect( |
| () => LibrariesSpecification.load(thisUri, |
| read({thisUri: thisJsonString, otherUri: otherJsonString})), |
| throwsA(checkException(messageCyclicSpec(thisUri)))); |
| }); |
| |
| test('include entry cannot be cyclic internally', () async { |
| var jsonString = ''' |
| { |
| "target": { |
| "include": [{"target": "target"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA( |
| checkException(messageCyclicInternalInclude('target', specUri)))); |
| |
| jsonString = ''' |
| { |
| "a": { |
| "include": [{"target": "b"}], |
| "libraries": {} |
| }, |
| "b": { |
| "include": [{"target": "a"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| expect( |
| () => |
| LibrariesSpecification.load(specUri, read({specUri: jsonString})), |
| throwsA(checkException(messageCyclicInternalInclude('a', specUri)))); |
| }); |
| |
| test('include works transitively', () async { |
| var thisFile = 'f.json'; |
| var thisUri = specUri.resolve(thisFile); |
| var otherFile1 = 'g.json'; |
| var otherUri1 = thisUri.resolve(otherFile1); |
| var otherFile2 = 'subfolder/h.json'; |
| var otherUri2 = thisUri.resolve(otherFile2); |
| var otherFile3 = '../i.json'; |
| var otherUri3 = otherUri2.resolve(otherFile3); |
| var thisJsonString = ''' |
| { |
| "foo": { |
| "include": [ |
| { |
| "path": "$otherFile1", |
| "target": "foo" |
| }, |
| { |
| "path": "$otherFile2", |
| "target": "foo" |
| } |
| ], |
| "libraries": {} |
| }, |
| "bar": { |
| "include": [{"path": "$otherFile2", "target": "bar"}], |
| "libraries": {} |
| } |
| } |
| '''; |
| var otherJsonString1 = ''' |
| { |
| "foo": { |
| "libraries": { |
| "foo": { |
| "uri": "foo.dart" |
| } |
| } |
| } |
| }'''; |
| var otherJsonString2 = ''' |
| { |
| "foo": { |
| "libraries": { |
| "foo_extra": { |
| "uri": "foo_extra.dart" |
| } |
| } |
| }, |
| "bar": { |
| "include": [{"path": "$otherFile3", "target": "baz"}], |
| "libraries": { |
| "bar": { |
| "uri": "bar/baz.dart" |
| } |
| } |
| } |
| }'''; |
| var otherJsonString3 = ''' |
| { |
| "baz": { |
| "libraries": { |
| "baz": { |
| "uri": "bar/baz.dart" |
| } |
| } |
| } |
| }'''; |
| var spec = await LibrariesSpecification.load( |
| thisUri, |
| read({ |
| thisUri: thisJsonString, |
| otherUri1: otherJsonString1, |
| otherUri2: otherJsonString2, |
| otherUri3: otherJsonString3, |
| })); |
| |
| expect(spec.specificationFor('foo').libraryInfoFor('foo')!.uri, |
| otherUri1.resolve('foo.dart')); |
| expect(spec.specificationFor('foo').libraryInfoFor('foo_extra')!.uri, |
| otherUri2.resolve('foo_extra.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('bar')!.uri, |
| otherUri2.resolve('bar/baz.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('baz')!.uri, |
| otherUri3.resolve('bar/baz.dart')); |
| }); |
| |
| test('internal include works transitively', () async { |
| var jsonString = ''' |
| { |
| "foo": { |
| "include": [{"target": "common"}], |
| "libraries": { |
| "foo": { |
| "uri": "foo.dart" |
| } |
| } |
| }, |
| "bar": { |
| "include": [ |
| { |
| "target": "common" |
| }, |
| { |
| "target": "baz" |
| } |
| ], |
| "libraries": { |
| "bar": { |
| "uri": "bar.dart" |
| } |
| } |
| }, |
| "common": { |
| "libraries": { |
| "common": { |
| "uri": "common.dart" |
| } |
| } |
| }, |
| "baz": { |
| "include": [{"target": "boz"}], |
| "libraries": { |
| "baz": { |
| "uri": "bar/baz.dart" |
| } |
| } |
| }, |
| "boz": { |
| "libraries": { |
| "boz": { |
| "uri": "bar/boz.dart" |
| } |
| } |
| } |
| } |
| '''; |
| var spec = await LibrariesSpecification.load( |
| specUri, read({specUri: jsonString})); |
| |
| expect(spec.specificationFor('foo').libraryInfoFor('foo')!.uri, |
| specUri.resolve('foo.dart')); |
| expect(spec.specificationFor('foo').libraryInfoFor('common')!.uri, |
| specUri.resolve('common.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('bar')!.uri, |
| specUri.resolve('bar.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('common')!.uri, |
| specUri.resolve('common.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('baz')!.uri, |
| specUri.resolve('bar/baz.dart')); |
| expect(spec.specificationFor('bar').libraryInfoFor('boz')!.uri, |
| specUri.resolve('bar/boz.dart')); |
| }); |
| }); |
| |
| group('toJson', () { |
| test('serialization produces same data that was parsed', () async { |
| var jsonString = ''' |
| { |
| "vm": { |
| "libraries": { |
| "foo" : { |
| "uri": "a/main.dart", |
| "patches": [ |
| "a/p1.dart", |
| "a/p2.dart" |
| ], |
| "supported": false |
| }, |
| "bar" : { |
| "uri": "b/main.dart", |
| "patches": [ |
| "b/p3.dart" |
| ] |
| } |
| } |
| }, |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart", |
| "patches": [] |
| } |
| } |
| } |
| } |
| '''; |
| |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| var newJson = |
| spec.toJsonString(Uri.parse('org-dartlang-test:///one/two/g.json')); |
| expect(newJson, jsonString.replaceAll(new RegExp('\\s'), '')); |
| }); |
| |
| test('serialization can adapt to new file location', () async { |
| var jsonString = ''' |
| { |
| "vm": { |
| "libraries": { |
| "foo" : { |
| "uri": "a/main.dart", |
| "patches": [ |
| "a/p1.dart", |
| "a/p2.dart" |
| ] |
| }, |
| "bar" : { |
| "uri": "b/main.dart", |
| "patches": [ |
| "b/p3.dart" |
| ] |
| } |
| } |
| }, |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "c/main.dart" |
| } |
| } |
| } |
| } |
| '''; |
| |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var spec = |
| await LibrariesSpecification.load(uri, read({uri: jsonString})); |
| var newJson = |
| spec.toJsonString(Uri.parse('org-dartlang-test:///one/g.json')); |
| |
| var expected = ''' |
| { |
| "vm": { |
| "libraries": { |
| "foo" : { |
| "uri": "two/a/main.dart", |
| "patches": [ |
| "two/a/p1.dart", |
| "two/a/p2.dart" |
| ] |
| }, |
| "bar" : { |
| "uri": "two/b/main.dart", |
| "patches": [ |
| "two/b/p3.dart" |
| ] |
| } |
| } |
| }, |
| "none": { |
| "libraries": { |
| "c" : { |
| "uri": "two/c/main.dart", |
| "patches": [] |
| } |
| } |
| } |
| } |
| '''; |
| |
| expect(newJson, expected.replaceAll(new RegExp('\\s'), '')); |
| }); |
| |
| test('serialization of nested specs inlines data', () async { |
| var jsonString = ''' |
| { |
| "vm": { |
| "include": [ |
| { |
| "path": "g.json", |
| "target": "vm" |
| } |
| ], |
| "libraries": { |
| "foo" : { |
| "uri": "a/main.dart" |
| } |
| } |
| } |
| } |
| '''; |
| var otherJsonString = ''' |
| { |
| "vm": { |
| "libraries": { |
| "bar" : { |
| "uri": "b/main.dart" |
| } |
| } |
| }, |
| "unused": { |
| "libraries": { |
| "foo" : { |
| "uri": "c/main.dart" |
| } |
| } |
| } |
| } |
| '''; |
| |
| var uri = Uri.parse('org-dartlang-test:///one/two/f.json'); |
| var otherUri = Uri.parse('org-dartlang-test:///one/two/g.json'); |
| var spec = await LibrariesSpecification.load( |
| uri, read({uri: jsonString, otherUri: otherJsonString})); |
| var newJson = |
| spec.toJsonString(Uri.parse('org-dartlang-test:///one/two/h.json')); |
| expect( |
| newJson, |
| ''' |
| { |
| "vm": { |
| "libraries": { |
| "bar" : { |
| "uri": "b/main.dart", |
| "patches": [] |
| }, |
| "foo" : { |
| "uri": "a/main.dart", |
| "patches": [] |
| } |
| } |
| } |
| } |
| ''' |
| .replaceAll(new RegExp('\\s'), '')); |
| }); |
| }); |
| } |