blob: 174a099e00605e9ea91b1fdc992f218904ea28b5 [file] [log] [blame]
// 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'));
});
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/'},
]
}));
// 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:///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'));
});
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>()));
});
}
void testThrowsContains(
String name, String source, String containsString) {
test(name, () {
dynamic exception;
try {
parsePackageConfigBytes(utf8.encode(source) as Uint8List,
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"}]}',
) as Uint8List,
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);
});
}