blob: 621902391b23ea7931c143f4cabc5540affa79a1 [file] [log] [blame]
// Copyright (c) 2016, 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';
import 'dart:convert';
import 'package:path/path.dart' as p;
import 'package:pub/src/io.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:yaml/yaml.dart';
import 'descriptor.dart' as d;
import 'test_pub.dart';
/// The current global [PackageServer].
PackageServer get globalPackageServer => _globalPackageServer;
PackageServer _globalPackageServer;
/// Creates an HTTP server that replicates the structure of pub.dartlang.org and
/// makes it the current [globalServer].
///
/// Calls [callback] with a [PackageServerBuilder] that's used to specify
/// which packages to serve.
Future servePackages(void callback(PackageServerBuilder builder)) async {
_globalPackageServer = await PackageServer.start(callback);
globalServer = _globalPackageServer._inner;
addTearDown(() {
_globalPackageServer = null;
});
}
/// Like [servePackages], but instead creates an empty server with no packages
/// registered.
///
/// This will always replace a previous server.
Future serveNoPackages() => servePackages((_) {});
/// A shortcut for [servePackages] that serves the version of barback used by
/// pub.
Future serveBarback() {
return servePackages((builder) {
builder.serveRealPackage('barback');
});
}
class PackageServer {
/// The inner [DescriptorServer] that this uses to serve its descriptors.
DescriptorServer _inner;
/// The [d.DirectoryDescriptor] describing the server layout of
/// `/api/packages` on the test server.
///
/// This contains metadata for packages that are being served via
/// [servePackages].
final _servedApiPackageDir = d.dir('packages', []);
/// The [d.DirectoryDescriptor] describing the server layout of `/packages` on
/// the test server.
///
/// This contains the tarballs for packages that are being served via
/// [servePackages].
final _servedPackageDir = d.dir('packages', []);
/// The current [PackageServerBuilder] that a user uses to specify which
/// package to serve.
///
/// This is preserved so that additional packages can be added.
final _builder = new PackageServerBuilder._();
/// A future that will complete to the port used for the server.
int get port => _inner.port;
/// A future that will complete to the URL for the server.
String get url => 'http://localhost:$port';
/// Creates an HTTP server that replicates the structure of pub.dartlang.org.
///
/// Calls [callback] with a [PackageServerBuilder] that's used to specify
/// which packages to serve.
static Future<PackageServer> start(
void callback(PackageServerBuilder builder)) async {
var descriptorServer = await DescriptorServer.start();
var server = new PackageServer._(descriptorServer);
descriptorServer.contents
..add(d.dir('api', [server._servedApiPackageDir]))
..add(server._servedPackageDir);
server.add(callback);
return server;
}
PackageServer._(this._inner);
/// Add to the current set of packages that are being served.
void add(void callback(PackageServerBuilder builder)) {
callback(_builder);
_servedApiPackageDir.contents.clear();
_servedPackageDir.contents.clear();
_builder._packages.forEach((name, versions) {
_servedApiPackageDir.contents.addAll([
d.file(
'$name',
JSON.encode({
'name': name,
'uploaders': ['nweiz@google.com'],
'versions': versions
.map((version) => packageVersionApiMap(version.pubspec))
.toList()
})),
d.dir(name, [
d.dir('versions', versions.map((version) {
return d.file(version.version.toString(),
JSON.encode(packageVersionApiMap(version.pubspec, full: true)));
}))
])
]);
_servedPackageDir.contents.add(d.dir(name, [
d.dir(
'versions',
versions.map((version) =>
d.tar('${version.version}.tar.gz', version.contents)))
]));
});
}
/// Returns the path of [package] at [version], installed from this server, in
/// the pub cache.
String pathInCache(String package, String version) => p.join(
d.sandbox, cachePath, "hosted/localhost%58$port/$package-$version");
/// Replace the current set of packages that are being served.
void replace(void callback(PackageServerBuilder builder)) {
_builder._clear();
add(callback);
}
}
/// A builder for specifying which packages should be served by [servePackages].
class PackageServerBuilder {
/// A map from package names to a list of concrete packages to serve.
final _packages = new Map<String, List<_ServedPackage>>();
PackageServerBuilder._();
/// Specifies that a package named [name] with [version] should be served.
///
/// If [deps] is passed, it's used as the "dependencies" field of the pubspec.
/// If [pubspec] is passed, it's used as the rest of the pubspec.
///
/// If [contents] is passed, it's used as the contents of the package. By
/// default, a package just contains a dummy lib directory.
void serve(String name, String version,
{Map<String, dynamic> deps,
Map<String, dynamic> pubspec,
Iterable<d.Descriptor> contents}) {
var pubspecFields = <String, dynamic>{"name": name, "version": version};
if (pubspec != null) pubspecFields.addAll(pubspec);
if (deps != null) pubspecFields["dependencies"] = deps;
if (contents == null) contents = [d.libDir(name, "$name $version")];
contents = [d.file("pubspec.yaml", yaml(pubspecFields))]..addAll(contents);
var packages = _packages.putIfAbsent(name, () => []);
packages.add(new _ServedPackage(pubspecFields, contents));
}
/// Serves the versions of [package] and all its dependencies that are
/// currently depended on by pub.
void serveRealPackage(String package) {
_addPackage(name) {
if (_packages.containsKey(name)) return;
_packages[name] = [];
var root = packagePath(name);
var pubspec =
new Map.from(loadYaml(readTextFile(p.join(root, 'pubspec.yaml'))));
// Remove any SDK constraints since we don't have a valid SDK version
// while testing.
pubspec.remove('environment');
_packages[name].add(new _ServedPackage(pubspec, [
d.file('pubspec.yaml', yaml(pubspec)),
new d.DirectoryDescriptor.fromFilesystem('lib', p.join(root, 'lib'))
]));
if (pubspec.containsKey('dependencies')) {
pubspec['dependencies'].keys.forEach(_addPackage);
}
}
_addPackage(package);
}
/// Clears all existing packages from this builder.
void _clear() {
_packages.clear();
}
}
/// A package that's intended to be served.
class _ServedPackage {
final Map pubspec;
final List<d.Descriptor> contents;
Version get version => new Version.parse(pubspec['version']);
_ServedPackage(this.pubspec, this.contents);
}