| // 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:test/test.dart'; |
| |
| import 'package:package_config/package_config_types.dart'; |
| import 'package:package_config/src/packages_file.dart' as packages; |
| import 'package:package_config/src/package_config_json.dart'; |
| import 'src/util.dart'; |
| |
| void throwError(Object error) => throw error; |
| |
| void main() { |
| group('.packages', () { |
| test('valid', () { |
| var packagesFile = '# Generated by pub yadda yadda\n' |
| 'foo:file:///foo/lib/\n' |
| 'bar:/bar/lib/\n' |
| 'baz:lib/\n'; |
| var result = packages.parse(utf8.encode(packagesFile), |
| Uri.parse('file:///tmp/file.dart'), throwError); |
| expect(result.version, 1); |
| expect({for (var p in result.packages) p.name}, {'foo', 'bar', 'baz'}); |
| expect(result.resolve(pkg('foo', 'foo.dart')), |
| Uri.parse('file:///foo/lib/foo.dart')); |
| expect(result.resolve(pkg('bar', 'bar.dart')), |
| Uri.parse('file:///bar/lib/bar.dart')); |
| expect(result.resolve(pkg('baz', 'baz.dart')), |
| Uri.parse('file:///tmp/lib/baz.dart')); |
| |
| var foo = result['foo']!; |
| expect(foo, isNotNull); |
| expect(foo.root, Uri.parse('file:///foo/')); |
| expect(foo.packageUriRoot, Uri.parse('file:///foo/lib/')); |
| expect(foo.languageVersion, LanguageVersion(2, 7)); |
| expect(foo.relativeRoot, false); |
| }); |
| |
| test('valid empty', () { |
| var packagesFile = '# Generated by pub yadda yadda\n'; |
| var result = packages.parse( |
| utf8.encode(packagesFile), Uri.file('/tmp/file.dart'), throwError); |
| expect(result.version, 1); |
| expect({for (var p in result.packages) p.name}, <String>{}); |
| }); |
| |
| group('invalid', () { |
| var baseFile = Uri.file('/tmp/file.dart'); |
| void testThrows(String name, String content) { |
| test(name, () { |
| expect( |
| () => packages.parse(utf8.encode(content), baseFile, throwError), |
| throwsA(TypeMatcher<FormatException>())); |
| }); |
| test(name + ', handle error', () { |
| var hadError = false; |
| packages.parse(utf8.encode(content), baseFile, (error) { |
| hadError = true; |
| expect(error, isA<FormatException>()); |
| }); |
| expect(hadError, true); |
| }); |
| } |
| |
| testThrows('repeated package name', 'foo:lib/\nfoo:lib\n'); |
| testThrows('no colon', 'foo\n'); |
| testThrows('empty package name', ':lib/\n'); |
| testThrows('dot only package name', '.:lib/\n'); |
| testThrows('dot only package name', '..:lib/\n'); |
| testThrows('invalid package name character', 'f\\o:lib/\n'); |
| testThrows('package URI', 'foo:package:bar/lib/'); |
| testThrows('location with query', 'f\\o:lib/?\n'); |
| testThrows('location with fragment', 'f\\o:lib/#\n'); |
| }); |
| }); |
| |
| 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( |
| // ignore: unnecessary_cast |
| utf8.encode(packageConfigFile) as Uint8List, |
| 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( |
| // ignore: unnecessary_cast |
| utf8.encode(packageConfigFile) as Uint8List, |
| 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( |
| // ignore: unnecessary_cast |
| utf8.encode('{$cfg,$pkgs}') as Uint8List, |
| 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( |
| // ignore: unnecessary_cast |
| utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List, |
| 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/'}, |
| ] |
| })); |
| // ignore: unnecessary_cast |
| var config = parsePackageConfigBytes(configBytes as Uint8List, |
| 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')); |
| }); |
| |
| group('invalid', () { |
| void testThrows(String name, String source) { |
| test(name, () { |
| expect( |
| // ignore: unnecessary_cast |
| () => parsePackageConfigBytes(utf8.encode(source) as Uint8List, |
| Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError), |
| throwsA(TypeMatcher<FormatException>())); |
| }); |
| } |
| |
| 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/"}]}'); |
| testThrows('same roots', |
| '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}'); |
| testThrows( |
| // The roots of foo and bar are the same. |
| 'same roots', |
| '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}'); |
| testThrows( |
| // 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"]}'); |
| }); |
| }); |
| |
| 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); |
| }); |
| } |