blob: e7e7d7aa97f5032d1e546a06ea846836fef153ab [file] [log] [blame]
// Copyright (c) 2013, 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:async' show Future;
import 'dart:convert' show JsonEncoder, json, utf8;
import 'dart:io' show File;
import 'package:path/path.dart' as p;
import 'package:pub/src/package_config.dart';
import 'package:pub/src/packages_file.dart' as packages_file;
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart';
import '../test_pub.dart';
// Resolve against a dummy URL so that we can test whether the URLs in
// the package file are themselves relative. We can't resolve against just
// "." due to sdk#23809.
const _base = '/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p';
/// Describes a `.packages` file and its contents.
class PackagesFileDescriptor extends Descriptor {
/// A map from package names to either version strings or path to the package.
final Map<String, String> _dependencies;
/// Describes a `.packages` file with the given dependencies.
///
/// [dependencies] maps package names to strings describing where the packages
/// are located on disk.
PackagesFileDescriptor([this._dependencies]) : super('.packages');
@override
Future create([String parent]) {
var contents = const <int>[];
if (_dependencies != null) {
var mapping = <String, Uri>{};
_dependencies.forEach((package, version) {
String packagePath;
if (_isSemver(version)) {
// It's a cache reference.
packagePath = p.join(cachePath, '$package-$version');
} else {
// Otherwise it's a path relative to the pubspec file,
// which is also relative to the .packages file.
packagePath = version;
}
mapping[package] = p.toUri(p.join(packagePath, 'lib', ''));
});
var buffer = StringBuffer();
packages_file.write(buffer, mapping);
contents = utf8.encode(buffer.toString());
}
return File(p.join(parent ?? sandbox, name)).writeAsBytes(contents);
}
@override
Future validate([String parent]) async {
var fullPath = p.join(parent ?? sandbox, name);
if (!await File(fullPath).exists()) {
fail("File not found: '$fullPath'.");
}
var bytes = await File(fullPath).readAsBytes();
var map = packages_file.parse(bytes, Uri.parse(_base));
for (var package in _dependencies.keys) {
if (!map.containsKey(package)) {
fail('.packages does not contain $package entry');
}
var description = _dependencies[package];
if (_isSemver(description)) {
if (!map[package].path.contains(description)) {
fail('.packages of $package has incorrect version. '
'Expected $description, found location: ${map[package]}.');
}
} else {
var expected = p.normalize(p.join(description, 'lib'));
var actual = p.normalize(p.fromUri(
p.url.relative(map[package].toString(), from: p.dirname(_base))));
if (expected != actual) {
fail('Relative path: Expected $expected, found $actual');
}
}
}
if (map.length != _dependencies.length) {
for (var key in map.keys) {
if (!_dependencies.containsKey(key)) {
fail('.packages file contains unexpected entry: $key');
}
}
}
}
@override
String describe() => name;
}
/// Describes a `.dart_tools/package_config.json` file and its contents.
class PackageConfigFileDescriptor extends Descriptor {
final String _generatorVersion;
/// A map describing the packages in this `package_config.json` file.
final List<PackageConfigEntry> _packages;
PackageConfig get _config {
return PackageConfig(
configVersion: 2,
packages: _packages,
generatorVersion: Version.parse(_generatorVersion),
generator: 'pub',
generated: DateTime.now().toUtc(),
);
}
/// Describes a `.packages` file with the given dependencies.
///
/// [dependencies] maps package names to strings describing where the packages
/// are located on disk.
PackageConfigFileDescriptor(this._packages, this._generatorVersion)
: super('.dart_tool/package_config.json');
@override
Future<void> create([String parent]) async {
final packageConfigFile = File(p.join(parent ?? sandbox, name));
await packageConfigFile.parent.create();
await packageConfigFile.writeAsString(
const JsonEncoder.withIndent(' ').convert(_config.toJson()) + '\n',
);
}
@override
Future<void> validate([String parent]) async {
final packageConfigFile = p.join(parent ?? sandbox, name);
if (!await File(packageConfigFile).exists()) {
fail("File not found: '$packageConfigFile'.");
}
Map<String, Object> rawJson = json.decode(
await File(packageConfigFile).readAsString(),
);
PackageConfig config;
try {
config = PackageConfig.fromJson(rawJson);
} on FormatException catch (e) {
fail('File "$packageConfigFile" is not valid: $e');
}
// Compare packages as sets to ignore ordering.
expect(
config.packages.map((e) => e.toJson()).toSet(),
equals(_packages.map((e) => e.toJson()).toSet()),
reason:
'"packages" property in "$packageConfigFile" does not expected values',
);
final expected = PackageConfig.fromJson(_config.toJson());
// omit generated date-time and packages
expected.generated = null; // comparing timestamps is unnecessary.
config.generated = null;
expected.packages = []; // Already compared packages (ignoring ordering)
config.packages = [];
expect(config.toJson(), equals(expected.toJson()),
reason: '"$packageConfigFile" does not match expected values');
}
@override
String describe() => name;
}
/// Returns `true` if [text] is a valid semantic version number string.
bool _isSemver(String text) {
try {
// See if it's a semver.
Version.parse(text);
return true;
} on FormatException catch (_) {
// Do nothing.
}
return false;
}