| // Copyright (c) 2019, 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 'dart:convert'; | |
| import 'dart:typed_data'; | |
| import 'package:package_config/package_config_types.dart'; | |
| import 'package:package_config/src/errors.dart'; | |
| import 'package:package_config/src/package_config_json.dart'; | |
| import 'package:test/test.dart'; | |
| import 'src/util.dart'; | |
| void main() { | |
| group('package_config.json', () { | |
| test('valid', () { | |
| var packageConfigFile = ''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "foo", | |
| "rootUri": "file:///foo/", | |
| "packageUri": "lib/", | |
| "languageVersion": "2.5", | |
| "nonstandard": true | |
| }, | |
| { | |
| "name": "bar", | |
| "rootUri": "/bar/", | |
| "packageUri": "lib/", | |
| "languageVersion": "9999.9999" | |
| }, | |
| { | |
| "name": "baz", | |
| "rootUri": "../", | |
| "packageUri": "lib/" | |
| }, | |
| { | |
| "name": "noslash", | |
| "rootUri": "../noslash", | |
| "packageUri": "lib" | |
| } | |
| ], | |
| "generator": "pub", | |
| "other": [42] | |
| } | |
| '''; | |
| var config = parsePackageConfigBytes( | |
| utf8.encode(packageConfigFile), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect( | |
| {for (var p in config.packages) p.name}, | |
| {'foo', 'bar', 'baz', 'noslash'}, | |
| ); | |
| expect( | |
| config.resolve(pkg('foo', 'foo.dart')), | |
| Uri.parse('file:///foo/lib/foo.dart'), | |
| ); | |
| expect( | |
| config.resolve(pkg('bar', 'bar.dart')), | |
| Uri.parse('file:///bar/lib/bar.dart'), | |
| ); | |
| expect( | |
| config.resolve(pkg('baz', 'baz.dart')), | |
| Uri.parse('file:///tmp/lib/baz.dart'), | |
| ); | |
| var foo = config['foo']!; | |
| expect(foo, isNotNull); | |
| expect(foo.root, Uri.parse('file:///foo/')); | |
| expect(foo.packageUriRoot, Uri.parse('file:///foo/lib/')); | |
| expect(foo.languageVersion, LanguageVersion(2, 5)); | |
| expect(foo.extraData, {'nonstandard': true}); | |
| expect(foo.relativeRoot, false); | |
| var bar = config['bar']!; | |
| expect(bar, isNotNull); | |
| expect(bar.root, Uri.parse('file:///bar/')); | |
| expect(bar.packageUriRoot, Uri.parse('file:///bar/lib/')); | |
| expect(bar.languageVersion, LanguageVersion(9999, 9999)); | |
| expect(bar.extraData, null); | |
| expect(bar.relativeRoot, false); | |
| var baz = config['baz']!; | |
| expect(baz, isNotNull); | |
| expect(baz.root, Uri.parse('file:///tmp/')); | |
| expect(baz.packageUriRoot, Uri.parse('file:///tmp/lib/')); | |
| expect(baz.languageVersion, null); | |
| expect(baz.relativeRoot, true); | |
| // No slash after root or package root. One is inserted. | |
| var noslash = config['noslash']!; | |
| expect(noslash, isNotNull); | |
| expect(noslash.root, Uri.parse('file:///tmp/noslash/')); | |
| expect(noslash.packageUriRoot, Uri.parse('file:///tmp/noslash/lib/')); | |
| expect(noslash.languageVersion, null); | |
| expect(noslash.relativeRoot, true); | |
| expect(config.extraData, { | |
| 'generator': 'pub', | |
| 'other': [42], | |
| }); | |
| }); | |
| test('valid other order', () { | |
| // The ordering in the file is not important. | |
| var packageConfigFile = ''' | |
| { | |
| "generator": "pub", | |
| "other": [42], | |
| "packages": [ | |
| { | |
| "languageVersion": "2.5", | |
| "packageUri": "lib/", | |
| "rootUri": "file:///foo/", | |
| "name": "foo" | |
| }, | |
| { | |
| "packageUri": "lib/", | |
| "languageVersion": "9999.9999", | |
| "rootUri": "/bar/", | |
| "name": "bar" | |
| }, | |
| { | |
| "packageUri": "lib/", | |
| "name": "baz", | |
| "rootUri": "../" | |
| } | |
| ], | |
| "configVersion": 2 | |
| } | |
| '''; | |
| var config = parsePackageConfigBytes( | |
| utf8.encode(packageConfigFile), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect({for (var p in config.packages) p.name}, {'foo', 'bar', 'baz'}); | |
| expect( | |
| config.resolve(pkg('foo', 'foo.dart')), | |
| Uri.parse('file:///foo/lib/foo.dart'), | |
| ); | |
| expect( | |
| config.resolve(pkg('bar', 'bar.dart')), | |
| Uri.parse('file:///bar/lib/bar.dart'), | |
| ); | |
| expect( | |
| config.resolve(pkg('baz', 'baz.dart')), | |
| Uri.parse('file:///tmp/lib/baz.dart'), | |
| ); | |
| expect(config.extraData, { | |
| 'generator': 'pub', | |
| 'other': [42], | |
| }); | |
| }); | |
| // Check that a few minimal configurations are valid. | |
| // These form the basis of invalid tests below. | |
| var cfg = '"configVersion":2'; | |
| var pkgs = '"packages":[]'; | |
| var name = '"name":"foo"'; | |
| var root = '"rootUri":"/foo/"'; | |
| test('minimal', () { | |
| var config = parsePackageConfigBytes( | |
| utf8.encode('{$cfg,$pkgs}'), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect(config.packages, isEmpty); | |
| }); | |
| test('minimal package', () { | |
| // A package must have a name and a rootUri, the remaining properties | |
| // are optional. | |
| var config = parsePackageConfigBytes( | |
| utf8.encode('{$cfg,"packages":[{$name,$root}]}'), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect(config.packages.first.name, 'foo'); | |
| }); | |
| test('nested packages', () { | |
| var configBytes = utf8.encode( | |
| json.encode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| {'name': 'foo', 'rootUri': '/foo/', 'packageUri': 'lib/'}, | |
| {'name': 'bar', 'rootUri': '/foo/bar/', 'packageUri': 'lib/'}, | |
| {'name': 'baz', 'rootUri': '/foo/bar/baz/', 'packageUri': 'lib/'}, | |
| {'name': 'qux', 'rootUri': '/foo/qux/', 'packageUri': 'lib/'}, | |
| ], | |
| }), | |
| ); | |
| var config = parsePackageConfigBytes( | |
| configBytes, | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect( | |
| config.packageOf(Uri.parse('file:///foo/lala/lala.dart'))!.name, | |
| 'foo', | |
| ); | |
| expect( | |
| config.packageOf(Uri.parse('file:///foo/bar/lala.dart'))!.name, | |
| 'bar', | |
| ); | |
| expect( | |
| config.packageOf(Uri.parse('file:///foo/bar/baz/lala.dart'))!.name, | |
| 'baz', | |
| ); | |
| expect( | |
| config.packageOf(Uri.parse('file:///foo/qux/lala.dart'))!.name, | |
| 'qux', | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///foo/lib/diz')), | |
| Uri.parse('package:foo/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///foo/bar/lib/diz')), | |
| Uri.parse('package:bar/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///foo/bar/baz/lib/diz')), | |
| Uri.parse('package:baz/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///foo/qux/lib/diz')), | |
| Uri.parse('package:qux/diz'), | |
| ); | |
| }); | |
| test('nested packages 2', () { | |
| var configBytes = utf8.encode( | |
| json.encode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| {'name': 'foo', 'rootUri': '/', 'packageUri': 'lib/'}, | |
| {'name': 'bar', 'rootUri': '/bar/', 'packageUri': 'lib/'}, | |
| {'name': 'baz', 'rootUri': '/bar/baz/', 'packageUri': 'lib/'}, | |
| {'name': 'qux', 'rootUri': '/qux/', 'packageUri': 'lib/'}, | |
| ], | |
| }), | |
| ); | |
| var config = parsePackageConfigBytes( | |
| configBytes, | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect( | |
| config.packageOf(Uri.parse('file:///lala/lala.dart'))!.name, | |
| 'foo', | |
| ); | |
| expect(config.packageOf(Uri.parse('file:///bar/lala.dart'))!.name, 'bar'); | |
| expect( | |
| config.packageOf(Uri.parse('file:///bar/baz/lala.dart'))!.name, | |
| 'baz', | |
| ); | |
| expect(config.packageOf(Uri.parse('file:///qux/lala.dart'))!.name, 'qux'); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///lib/diz')), | |
| Uri.parse('package:foo/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///bar/lib/diz')), | |
| Uri.parse('package:bar/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///bar/baz/lib/diz')), | |
| Uri.parse('package:baz/diz'), | |
| ); | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///qux/lib/diz')), | |
| Uri.parse('package:qux/diz'), | |
| ); | |
| }); | |
| test('packageOf is case sensitive on windows', () { | |
| var configBytes = utf8.encode( | |
| json.encode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| {'name': 'foo', 'rootUri': 'file:///C:/Foo/', 'packageUri': 'lib/'}, | |
| ], | |
| }), | |
| ); | |
| var config = parsePackageConfigBytes( | |
| configBytes, | |
| Uri.parse('file:///C:/tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect(config.version, 2); | |
| expect( | |
| config.packageOf(Uri.parse('file:///C:/foo/lala/lala.dart')), | |
| null, | |
| ); | |
| expect( | |
| config.packageOf(Uri.parse('file:///C:/Foo/lala/lala.dart'))!.name, | |
| 'foo', | |
| ); | |
| }); | |
| group('invalid', () { | |
| void testThrows(String name, String source) { | |
| test(name, () { | |
| expect( | |
| () => parsePackageConfigBytes( | |
| utf8.encode(source), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ), | |
| throwsA(isA<FormatException>()), | |
| ); | |
| }); | |
| } | |
| void testThrowsContains( | |
| String name, | |
| String source, | |
| String containsString, | |
| ) { | |
| test(name, () { | |
| Object? exception; | |
| try { | |
| parsePackageConfigBytes( | |
| utf8.encode(source), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| } catch (e) { | |
| exception = e; | |
| } | |
| if (exception == null) fail("Didn't get exception"); | |
| expect('$exception', contains(containsString)); | |
| }); | |
| } | |
| testThrows('comment', '# comment\n {$cfg,$pkgs}'); | |
| testThrows('.packages file', 'foo:/foo\n'); | |
| testThrows('no configVersion', '{$pkgs}'); | |
| testThrows('no packages', '{$cfg}'); | |
| group('config version:', () { | |
| testThrows('null', '{"configVersion":null,$pkgs}'); | |
| testThrows('string', '{"configVersion":"2",$pkgs}'); | |
| testThrows('array', '{"configVersion":[2],$pkgs}'); | |
| }); | |
| group('packages:', () { | |
| testThrows('null', '{$cfg,"packages":null}'); | |
| testThrows('string', '{$cfg,"packages":"foo"}'); | |
| testThrows('object', '{$cfg,"packages":{}}'); | |
| }); | |
| group('packages entry:', () { | |
| testThrows('null', '{$cfg,"packages":[null]}'); | |
| testThrows('string', '{$cfg,"packages":["foo"]}'); | |
| testThrows('array', '{$cfg,"packages":[[]]}'); | |
| }); | |
| group('package', () { | |
| testThrows('no name', '{$cfg,"packages":[{$root}]}'); | |
| group('name:', () { | |
| testThrows('null', '{$cfg,"packages":[{"name":null,$root}]}'); | |
| testThrows('num', '{$cfg,"packages":[{"name":1,$root}]}'); | |
| testThrows('object', '{$cfg,"packages":[{"name":{},$root}]}'); | |
| testThrows('empty', '{$cfg,"packages":[{"name":"",$root}]}'); | |
| testThrows('one-dot', '{$cfg,"packages":[{"name":".",$root}]}'); | |
| testThrows('two-dot', '{$cfg,"packages":[{"name":"..",$root}]}'); | |
| testThrows( | |
| "invalid char '\\'", | |
| '{$cfg,"packages":[{"name":"\\",$root}]}', | |
| ); | |
| testThrows( | |
| "invalid char ':'", | |
| '{$cfg,"packages":[{"name":":",$root}]}', | |
| ); | |
| testThrows( | |
| "invalid char ' '", | |
| '{$cfg,"packages":[{"name":" ",$root}]}', | |
| ); | |
| }); | |
| testThrows('no root', '{$cfg,"packages":[{$name}]}'); | |
| group('root:', () { | |
| testThrows('null', '{$cfg,"packages":[{$name,"rootUri":null}]}'); | |
| testThrows('num', '{$cfg,"packages":[{$name,"rootUri":1}]}'); | |
| testThrows('object', '{$cfg,"packages":[{$name,"rootUri":{}}]}'); | |
| testThrows('fragment', '{$cfg,"packages":[{$name,"rootUri":"x/#"}]}'); | |
| testThrows('query', '{$cfg,"packages":[{$name,"rootUri":"x/?"}]}'); | |
| testThrows( | |
| 'package-URI', | |
| '{$cfg,"packages":[{$name,"rootUri":"package:x/x/"}]}', | |
| ); | |
| }); | |
| group('package-URI root:', () { | |
| testThrows( | |
| 'null', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":null}]}', | |
| ); | |
| testThrows('num', '{$cfg,"packages":[{$name,$root,"packageUri":1}]}'); | |
| testThrows( | |
| 'object', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":{}}]}', | |
| ); | |
| testThrows( | |
| 'fragment', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":"x/#"}]}', | |
| ); | |
| testThrows( | |
| 'query', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":"x/?"}]}', | |
| ); | |
| testThrows( | |
| 'package: URI', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":"package:x/x/"}]}', | |
| ); | |
| testThrows( | |
| 'not inside root', | |
| '{$cfg,"packages":[{$name,$root,"packageUri":"../other/"}]}', | |
| ); | |
| }); | |
| group('language version', () { | |
| testThrows( | |
| 'null', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":null}]}', | |
| ); | |
| testThrows( | |
| 'num', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":1}]}', | |
| ); | |
| testThrows( | |
| 'object', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":{}}]}', | |
| ); | |
| testThrows( | |
| 'empty', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":""}]}', | |
| ); | |
| testThrows( | |
| 'non number.number', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"x.1"}]}', | |
| ); | |
| testThrows( | |
| 'number.non number', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1.x"}]}', | |
| ); | |
| testThrows( | |
| 'non number', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"x"}]}', | |
| ); | |
| testThrows( | |
| 'one number', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1"}]}', | |
| ); | |
| testThrows( | |
| 'three numbers', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1.2.3"}]}', | |
| ); | |
| testThrows( | |
| 'leading zero first', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"01.1"}]}', | |
| ); | |
| testThrows( | |
| 'leading zero second', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1.01"}]}', | |
| ); | |
| testThrows( | |
| 'trailing-', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1-1"}]}', | |
| ); | |
| testThrows( | |
| 'trailing+', | |
| '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1+1"}]}', | |
| ); | |
| }); | |
| }); | |
| testThrows( | |
| 'duplicate package name', | |
| '{$cfg,"packages":[{$name,$root},{$name,"rootUri":"/other/"}]}', | |
| ); | |
| testThrowsContains( | |
| // The roots of foo and bar are the same. | |
| 'same roots', | |
| '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}', | |
| 'the same root directory', | |
| ); | |
| testThrowsContains( | |
| // The roots of foo and bar are the same. | |
| 'same roots 2', | |
| '{$cfg,"packages":[{$name,"rootUri":"/"},{"name":"bar","rootUri":"/"}]}', | |
| 'the same root directory', | |
| ); | |
| testThrowsContains( | |
| // The root of bar is inside the root of foo, | |
| // but the package root of foo is inside the root of bar. | |
| 'between root and lib', | |
| '{$cfg,"packages":[' | |
| '{"name":"foo","rootUri":"/foo/","packageUri":"bar/lib/"},' | |
| '{"name":"bar","rootUri":"/foo/bar/","packageUri":"baz/lib"}]}', | |
| 'package root of foo is inside the root of bar', | |
| ); | |
| // This shouldn't be allowed, but for internal reasons it is. | |
| test('package inside package root', () { | |
| var config = parsePackageConfigBytes( | |
| utf8.encode( | |
| '{$cfg,"packages":[' | |
| '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},' | |
| '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}', | |
| ), | |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), | |
| throwError, | |
| ); | |
| expect( | |
| config | |
| .packageOf(Uri.parse('file:///foo/lib/bar/lib/lala.dart'))! | |
| .name, | |
| 'foo', | |
| ); // why not bar? | |
| expect( | |
| config.toPackageUri(Uri.parse('file:///foo/lib/bar/lib/diz')), | |
| Uri.parse('package:foo/bar/lib/diz'), | |
| ); // why not package:bar/diz? | |
| }); | |
| }); | |
| }); | |
| group('factories', () { | |
| void testConfig(String name, PackageConfig config, PackageConfig expected) { | |
| group(name, () { | |
| test('structure', () { | |
| expect(config.version, expected.version); | |
| var expectedPackages = {for (var p in expected.packages) p.name}; | |
| var actualPackages = {for (var p in config.packages) p.name}; | |
| expect(actualPackages, expectedPackages); | |
| }); | |
| for (var package in config.packages) { | |
| var name = package.name; | |
| test('package $name', () { | |
| var expectedPackage = expected[name]!; | |
| expect(expectedPackage, isNotNull); | |
| expect(package.root, expectedPackage.root, reason: 'root'); | |
| expect( | |
| package.packageUriRoot, | |
| expectedPackage.packageUriRoot, | |
| reason: 'package root', | |
| ); | |
| expect( | |
| package.languageVersion, | |
| expectedPackage.languageVersion, | |
| reason: 'languageVersion', | |
| ); | |
| }); | |
| } | |
| }); | |
| } | |
| var configText = ''' | |
| {"configVersion": 2, "packages": [ | |
| { | |
| "name": "foo", | |
| "rootUri": "foo/", | |
| "packageUri": "bar/", | |
| "languageVersion": "1.2" | |
| } | |
| ]} | |
| '''; | |
| var baseUri = Uri.parse('file:///start/'); | |
| var config = PackageConfig([ | |
| Package( | |
| 'foo', | |
| Uri.parse('file:///start/foo/'), | |
| packageUriRoot: Uri.parse('file:///start/foo/bar/'), | |
| languageVersion: LanguageVersion(1, 2), | |
| ), | |
| ]); | |
| testConfig( | |
| 'string', | |
| PackageConfig.parseString(configText, baseUri), | |
| config, | |
| ); | |
| testConfig( | |
| 'bytes', | |
| PackageConfig.parseBytes( | |
| Uint8List.fromList(configText.codeUnits), | |
| baseUri, | |
| ), | |
| config, | |
| ); | |
| testConfig( | |
| 'json', | |
| PackageConfig.parseJson(jsonDecode(configText), baseUri), | |
| config, | |
| ); | |
| baseUri = Uri.parse('file:///start2/'); | |
| config = PackageConfig([ | |
| Package( | |
| 'foo', | |
| Uri.parse('file:///start2/foo/'), | |
| packageUriRoot: Uri.parse('file:///start2/foo/bar/'), | |
| languageVersion: LanguageVersion(1, 2), | |
| ), | |
| ]); | |
| testConfig( | |
| 'string2', | |
| PackageConfig.parseString(configText, baseUri), | |
| config, | |
| ); | |
| testConfig( | |
| 'bytes2', | |
| PackageConfig.parseBytes( | |
| Uint8List.fromList(configText.codeUnits), | |
| baseUri, | |
| ), | |
| config, | |
| ); | |
| testConfig( | |
| 'json2', | |
| PackageConfig.parseJson(jsonDecode(configText), baseUri), | |
| config, | |
| ); | |
| }); | |
| } |