Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Unified Diff: utils/tests/pub/test_pub.dart

Issue 12437022: Use scheduled_test for Pub tests. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: re-upload Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: utils/tests/pub/test_pub.dart
diff --git a/utils/tests/pub/test_pub.dart b/utils/tests/pub/test_pub.dart
index bf573fd9d364eb01dad5eef02a78f3e27abd4bbe..ec4d827cf8b9663c233812195712ce136da9db33 100644
--- a/utils/tests/pub/test_pub.dart
+++ b/utils/tests/pub/test_pub.dart
@@ -19,8 +19,11 @@ import 'dart:utf';
import '../../../pkg/http/lib/testing.dart';
import '../../../pkg/oauth2/lib/oauth2.dart' as oauth2;
import '../../../pkg/pathos/lib/path.dart' as path;
-import '../../../pkg/unittest/lib/unittest.dart';
+import '../../../pkg/scheduled_test/lib/scheduled_process.dart';
+import '../../../pkg/scheduled_test/lib/scheduled_server.dart';
+import '../../../pkg/scheduled_test/lib/scheduled_test.dart';
import '../../../pkg/yaml/lib/yaml.dart';
+
import '../../lib/file_system.dart' as fs;
import '../../pub/entrypoint.dart';
// TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides
@@ -37,6 +40,7 @@ import '../../pub/system_cache.dart';
import '../../pub/utils.dart';
import '../../pub/validator.dart';
import 'command_line_config.dart';
+import 'descriptor.dart' as d;
/// This should be called at the top of a test file to set up an appropriate
/// test configuration for the machine running the tests.
@@ -47,33 +51,6 @@ initConfig() {
}
}
-/// Creates a new [FileDescriptor] with [name] and [contents].
-FileDescriptor file(Pattern name, String contents) =>
- new FileDescriptor(name, contents);
-
-/// Creates a new [FileDescriptor] with [name] and [contents].
-FileDescriptor binaryFile(Pattern name, List<int> contents) =>
- new FileDescriptor.bytes(name, contents);
-
-/// Creates a new [DirectoryDescriptor] with [name] and [contents].
-DirectoryDescriptor dir(Pattern name, [List<Descriptor> contents]) =>
- new DirectoryDescriptor(name, contents);
-
-/// Creates a new [FutureDescriptor] wrapping [future].
-FutureDescriptor async(Future<Descriptor> future) =>
- new FutureDescriptor(future);
-
-/// Creates a new [GitRepoDescriptor] with [name] and [contents].
-GitRepoDescriptor git(Pattern name, [List<Descriptor> contents]) =>
- new GitRepoDescriptor(name, contents);
-
-/// Creates a new [TarFileDescriptor] with [name] and [contents].
-TarFileDescriptor tar(Pattern name, [List<Descriptor> contents]) =>
- new TarFileDescriptor(name, contents);
-
-/// Creates a new [NothingDescriptor] with [name].
-NothingDescriptor nothing(String name) => new NothingDescriptor(name);
-
/// The current [HttpServer] created using [serve].
var _server;
@@ -84,9 +61,9 @@ Completer<int> _portCompleterCache;
Completer<int> get _portCompleter {
if (_portCompleterCache != null) return _portCompleterCache;
_portCompleterCache = new Completer<int>();
- _scheduleCleanup((_) {
+ currentSchedule.onComplete.schedule(() {
_portCompleterCache = null;
- });
+ }, 'clearing the port completer');
return _portCompleterCache;
}
@@ -97,45 +74,43 @@ Future<int> get port => _portCompleter.future;
/// exist only for the duration of the pub run.
///
/// Subsequent calls to [serve] will replace the previous server.
-void serve([List<Descriptor> contents]) {
- var baseDir = dir("serve-dir", contents);
+void serve([List<d.Descriptor> contents]) {
+ var baseDir = d.dir("serve-dir", contents);
- _schedule((_) {
+ schedule(() {
return _closeServer().then((_) {
return SafeHttpServer.bind("127.0.0.1", 0).then((server) {
_server = server;
server.listen((request) {
var response = request.response;
- var path = request.uri.path.replaceFirst("/", "").split("/");
- response.persistentConnection = false;
- var stream;
try {
- stream = baseDir.load(path);
+ var path = request.uri.path.replaceFirst("/", "");
+ response.persistentConnection = false;
+ var stream = baseDir.load(path);
+
+ new ByteStream(stream).toBytes().then((data) {
+ response.statusCode = 200;
+ response.contentLength = data.length;
+ response.writeBytes(data);
+ response.close();
+ }).catchError((e) {
+ response.statusCode = 404;
+ response.contentLength = 0;
+ response.close();
+ });
} catch (e) {
- response.statusCode = 404;
- response.contentLength = 0;
+ currentSchedule.signalError(e);
+ response.statusCode = 500;
response.close();
return;
}
-
- stream.toBytes().then((data) {
- response.statusCode = 200;
- response.contentLength = data.length;
- response.writeBytes(data);
- response.close();
- }).catchError((e) {
- print("Exception while handling ${request.uri}: $e");
- response.statusCode = 500;
- response.reasonPhrase = e.message;
- response.close();
- });
});
_portCompleter.complete(_server.port);
- _scheduleCleanup((_) => _closeServer());
+ currentSchedule.onComplete.schedule(_closeServer);
return null;
});
});
- });
+ }, 'starting a server serving:\n${baseDir.describe()}');
}
/// Closes [_server]. Returns a [Future] that will complete after the [_server]
@@ -151,10 +126,10 @@ Future _closeServer() {
return sleep(10);
}
-/// The [DirectoryDescriptor] describing the server layout of packages that are
-/// being served via [servePackages]. This is `null` if [servePackages] has not
-/// yet been called for this test.
-DirectoryDescriptor _servedPackageDir;
+/// The [d.DirectoryDescriptor] describing the server layout of packages that
+/// are being served via [servePackages]. This is `null` if [servePackages] has
+/// not yet been called for this test.
+d.DirectoryDescriptor _servedPackageDir;
/// A map from package names to version numbers to YAML-serialized pubspecs for
/// those packages. This represents the packages currently being served by
@@ -171,17 +146,17 @@ Map<String, Map<String, String>> _servedPackages;
void servePackages(List<Map> pubspecs) {
if (_servedPackages == null || _servedPackageDir == null) {
_servedPackages = <String, Map<String, String>>{};
- _servedPackageDir = dir('packages', []);
+ _servedPackageDir = d.dir('packages', []);
serve([_servedPackageDir]);
- _scheduleCleanup((_) {
+ currentSchedule.onComplete.schedule(() {
_servedPackages = null;
_servedPackageDir = null;
- });
+ }, 'cleaning up served packages');
}
- _schedule((_) {
- return _awaitObject(pubspecs).then((resolvedPubspecs) {
+ schedule(() {
+ return awaitObject(pubspecs).then((resolvedPubspecs) {
for (var spec in resolvedPubspecs) {
var name = spec['name'];
var version = spec['version'];
@@ -194,15 +169,14 @@ void servePackages(List<Map> pubspecs) {
for (var name in _servedPackages.keys) {
var versions = _servedPackages[name].keys.toList();
_servedPackageDir.contents.addAll([
- file('$name.json',
- json.stringify({'versions': versions})),
- dir(name, [
- dir('versions', flatten(versions.map((version) {
+ d.file('$name.json', json.stringify({'versions': versions})),
+ d.dir(name, [
+ d.dir('versions', flatten(versions.map((version) {
return [
- file('$version.yaml', _servedPackages[name][version]),
- tar('$version.tar.gz', [
- file('pubspec.yaml', _servedPackages[name][version]),
- libDir(name, '$name $version')
+ d.file('$version.yaml', _servedPackages[name][version]),
+ d.tar('$version.tar.gz', [
+ d.file('pubspec.yaml', _servedPackages[name][version]),
+ d.libDir(name, '$name $version')
])
];
})))
@@ -210,237 +184,12 @@ void servePackages(List<Map> pubspecs) {
]);
}
});
- });
+ }, 'initializing the package server');
}
/// Converts [value] into a YAML string.
String yaml(value) => json.stringify(value);
-/// Describes a package that passes all validation.
-Descriptor get normalPackage => dir(appPath, [
- libPubspec("test_pkg", "1.0.0"),
- file("LICENSE", "Eh, do what you want."),
- dir("lib", [
- file("test_pkg.dart", "int i = 1;")
- ])
-]);
-
-/// Describes a file named `pubspec.yaml` with the given YAML-serialized
-/// [contents], which should be a serializable object.
-///
-/// [contents] may contain [Future]s that resolve to serializable objects,
-/// which may in turn contain [Future]s recursively.
-Descriptor pubspec(Map contents) {
- return async(_awaitObject(contents).then((resolvedContents) =>
- file("pubspec.yaml", yaml(resolvedContents))));
-}
-
-/// Describes a file named `pubspec.yaml` for an application package with the
-/// given [dependencies].
-Descriptor appPubspec(List dependencies) {
- return pubspec({
- "name": "myapp",
- "dependencies": _dependencyListToMap(dependencies)
- });
-}
-
-/// Describes a file named `pubspec.yaml` for a library package with the given
-/// [name], [version], and [deps]. If "sdk" is given, then it adds an SDK
-/// constraint on that version.
-Descriptor libPubspec(String name, String version, {List deps, String sdk}) {
- var map = package(name, version, deps);
-
- if (sdk != null) {
- map["environment"] = {
- "sdk": sdk
- };
- }
-
- return pubspec(map);
-}
-
-/// Describes a directory named `lib` containing a single dart file named
-/// `<name>.dart` that contains a line of Dart code.
-Descriptor libDir(String name, [String code]) {
- // Default to printing the name if no other code was given.
- if (code == null) {
- code = name;
- }
-
- return dir("lib", [
- file("$name.dart", 'main() => "$code";')
- ]);
-}
-
-/// Describes a map representing a library package with the given [name],
-/// [version], and [dependencies].
-Map package(String name, String version, [List dependencies]) {
- var package = {
- "name": name,
- "version": version,
- "author": "Nathan Weizenbaum <nweiz@google.com>",
- "homepage": "http://pub.dartlang.org",
- "description": "A package, I guess."
- };
- if (dependencies != null) {
- package["dependencies"] = _dependencyListToMap(dependencies);
- }
- return package;
-}
-
-/// Describes a map representing a dependency on a package in the package
-/// repository.
-Map dependency(String name, [String versionConstraint]) {
- var url = port.then((p) => "http://localhost:$p");
- var dependency = {"hosted": {"name": name, "url": url}};
- if (versionConstraint != null) dependency["version"] = versionConstraint;
- return dependency;
-}
-
-/// Describes a directory for a package installed from the mock package server.
-/// This directory is of the form found in the global package cache.
-DirectoryDescriptor packageCacheDir(String name, String version) {
- return dir("$name-$version", [
- libDir(name, '$name $version')
- ]);
-}
-
-/// Describes a directory for a Git package. This directory is of the form
-/// found in the revision cache of the global package cache.
-DirectoryDescriptor gitPackageRevisionCacheDir(String name, [int modifier]) {
- var value = name;
- if (modifier != null) value = "$name $modifier";
- return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [
- libDir(name, value)
- ]);
-}
-
-/// Describes a directory for a Git package. This directory is of the form
-/// found in the repo cache of the global package cache.
-DirectoryDescriptor gitPackageRepoCacheDir(String name) {
- return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [
- dir('hooks'),
- dir('info'),
- dir('objects'),
- dir('refs')
- ]);
-}
-
-/// Describes the `packages/` directory containing all the given [packages],
-/// which should be name/version pairs. The packages will be validated against
-/// the format produced by the mock package server.
-///
-/// A package with a null version should not be installed.
-DirectoryDescriptor packagesDir(Map<String, String> packages) {
- var contents = <Descriptor>[];
- packages.forEach((name, version) {
- if (version == null) {
- contents.add(nothing(name));
- } else {
- contents.add(dir(name, [
- file("$name.dart", 'main() => "$name $version";')
- ]));
- }
- });
- return dir(packagesPath, contents);
-}
-
-/// Describes the global package cache directory containing all the given
-/// [packages], which should be name/version pairs. The packages will be
-/// validated against the format produced by the mock package server.
-///
-/// A package's value may also be a list of versions, in which case all
-/// versions are expected to be installed.
-DirectoryDescriptor cacheDir(Map packages) {
- var contents = <Descriptor>[];
- packages.forEach((name, versions) {
- if (versions is! List) versions = [versions];
- for (var version in versions) {
- contents.add(packageCacheDir(name, version));
- }
- });
- return dir(cachePath, [
- dir('hosted', [
- async(port.then((p) => dir('localhost%58$p', contents)))
- ])
- ]);
-}
-
-/// Describes the file in the system cache that contains the client's OAuth2
-/// credentials. The URL "/token" on [server] will be used as the token
-/// endpoint for refreshing the access token.
-Descriptor credentialsFile(
- ScheduledServer server,
- String accessToken,
- {String refreshToken,
- DateTime expiration}) {
- return async(server.url.then((url) {
- return dir(cachePath, [
- file('credentials.json', new oauth2.Credentials(
- accessToken,
- refreshToken,
- url.resolve('/token'),
- ['https://www.googleapis.com/auth/userinfo.email'],
- expiration).toJson())
- ]);
- }));
-}
-
-/// Describes the application directory, containing only a pubspec specifying
-/// the given [dependencies].
-DirectoryDescriptor appDir(List dependencies) =>
- dir(appPath, [appPubspec(dependencies)]);
-
-/// Converts a list of dependencies as passed to [package] into a hash as used
-/// in a pubspec.
-Future<Map> _dependencyListToMap(List<Map> dependencies) {
- return _awaitObject(dependencies).then((resolvedDependencies) {
- var result = <String, Map>{};
- for (var dependency in resolvedDependencies) {
- var keys = dependency.keys.where((key) => key != "version");
- var sourceName = only(keys);
- var source;
- switch (sourceName) {
- case "git":
- source = new GitSource();
- break;
- case "hosted":
- source = new HostedSource();
- break;
- case "path":
- source = new PathSource();
- break;
- default:
- throw 'Unknown source "$sourceName"';
- }
-
- result[_packageName(sourceName, dependency[sourceName])] = dependency;
- }
- return result;
- });
-}
-
-/// Return the name for the package described by [description] and from
-/// [sourceName].
-String _packageName(String sourceName, description) {
- switch (sourceName) {
- case "git":
- var url = description is String ? description : description['url'];
- // TODO(rnystrom): Using path.basename on a URL is hacky. If we add URL
- // support to pkg/pathos, should use an explicit builder for that.
- return path.basename(url.replaceFirst(new RegExp(r"(\.git)?/?$"), ""));
- case "hosted":
- if (description is String) return description;
- return description['name'];
- case "path":
- return path.basename(description);
- case "sdk":
- return description;
- default:
- return description;
- }
-}
-
/// The full path to the created sandbox directory for an integration test.
String get sandboxDir => _sandboxDir;
String _sandboxDir;
@@ -461,21 +210,6 @@ final String appPath = "myapp";
/// to the sandbox directory.
final String packagesPath = "$appPath/packages";
-/// The type for callbacks that will be fired during [schedulePub]. Takes the
-/// sandbox directory as a parameter.
-typedef Future _ScheduledEvent(String parentDir);
-
-/// The list of events that are scheduled to run as part of the test case.
-Queue<_ScheduledEvent> _scheduled;
-
-/// The list of events that are scheduled to run after the test case, even if
-/// it failed.
-Queue<_ScheduledEvent> _scheduledCleanup;
-
-/// The list of events that are scheduled to run after the test case only if it
-/// failed.
-Queue<_ScheduledEvent> _scheduledOnException;
-
/// Set to true when the current batch of scheduled events should be aborted.
bool _abortScheduled = false;
@@ -495,47 +229,17 @@ void solo_integration(String description, void body()) =>
void _integration(String description, void body(), [Function testFn]) {
testFn(description, () {
// Ensure the SDK version is always available.
- dir(sdkPath, [
- file('version', '0.1.2.3')
- ]).scheduleCreate();
+ d.dir(sdkPath, [
+ d.file('version', '0.1.2.3')
+ ]).create();
_sandboxDir = createTempDir();
+ d.defaultRoot = sandboxDir;
+ currentSchedule.onComplete.schedule(() => deleteDir(_sandboxDir),
+ 'deleting the sandbox directory');
// Schedule the test.
body();
-
- // Run all of the scheduled tasks. If an error occurs, it will propagate
- // through the futures back up to here where we can hand it off to unittest.
- var asyncDone = expectAsync0(() {});
- return timeout(_runScheduled(_scheduled),
- _TIMEOUT, 'waiting for a test to complete').catchError((e) {
- return _runScheduled(_scheduledOnException).then((_) {
- // Rethrow the original error so it keeps propagating.
- throw e;
- });
- }).whenComplete(() {
- // Clean up after ourselves. Do this first before reporting back to
- // unittest because it will advance to the next test immediately.
- return _runScheduled(_scheduledCleanup).then((_) {
- _scheduled = null;
- _scheduledCleanup = null;
- _scheduledOnException = null;
- if (_sandboxDir != null) {
- var dir = _sandboxDir;
- _sandboxDir = null;
- return deleteDir(dir);
- }
- });
- }).then((_) {
- // If we got here, the test completed successfully so tell unittest so.
- asyncDone();
- }).catchError((e) {
- // If we got here, an error occurred. We will register it with unittest
- // directly so that the error message isn't wrapped in any matcher stuff.
- // We do this call last because it will cause unittest to *synchronously*
- // advance to the next test and run it.
- registerException(e.error, e.stackTrace);
- });
});
}
@@ -551,60 +255,39 @@ String get testDirectory {
/// Schedules renaming (moving) the directory at [from] to [to], both of which
/// are assumed to be relative to [sandboxDir].
void scheduleRename(String from, String to) {
- _schedule((sandboxDir) {
- return renameDir(path.join(sandboxDir, from), path.join(sandboxDir, to));
- });
+ schedule(
+ () => renameDir(
+ path.join(sandboxDir, from),
+ path.join(sandboxDir, to)),
+ 'renaming $from to $to');
}
-
/// Schedules creating a symlink at path [symlink] that points to [target],
/// both of which are assumed to be relative to [sandboxDir].
void scheduleSymlink(String target, String symlink) {
- _schedule((sandboxDir) {
- return createSymlink(path.join(sandboxDir, target),
- path.join(sandboxDir, symlink));
- });
+ schedule(
+ () => createSymlink(
+ path.join(sandboxDir, target),
+ path.join(sandboxDir, symlink)),
+ 'symlinking $target to $symlink');
}
/// Schedules a call to the Pub command-line utility. Runs Pub with [args] and
/// validates that its results match [output], [error], and [exitCode].
void schedulePub({List args, Pattern output, Pattern error,
Future<Uri> tokenEndpoint, int exitCode: 0}) {
- _schedule((sandboxDir) {
- return _doPub(runProcess, sandboxDir, args, tokenEndpoint).then((result) {
- var failures = [];
-
- _validateOutput(failures, 'stdout', output, result.stdout);
- _validateOutput(failures, 'stderr', error, result.stderr);
-
- if (result.exitCode != exitCode) {
- failures.add(
- 'Pub returned exit code ${result.exitCode}, expected $exitCode.');
- }
-
- if (failures.length > 0) {
- if (error == null) {
- // If we aren't validating the error, still show it on failure.
- failures.add('Pub stderr:');
- failures.addAll(result.stderr.map((line) => '| $line'));
- }
-
- throw new TestFailure(failures.join('\n'));
- }
-
- return null;
- });
- });
-}
+ var pub = startPub(args: args, tokenEndpoint: tokenEndpoint);
+ pub.shouldExit(exitCode);
-/// Starts a Pub process and returns a [ScheduledProcess] that supports
-/// interaction with that process.
-///
-/// Any futures in [args] will be resolved before the process is started.
-ScheduledProcess startPub({List args, Future<Uri> tokenEndpoint}) {
- var process = _scheduleValue((sandboxDir) =>
- _doPub(startProcess, sandboxDir, args, tokenEndpoint));
- return new ScheduledProcess("pub", process);
+ expect(Future.wait([
+ pub.remainingStdout(),
+ pub.remainingStderr()
+ ]).then((results) {
+ var failures = [];
+ _validateOutput(failures, 'stdout', output, results[0].split('\n'));
+ _validateOutput(failures, 'stderr', error, results[1].split('\n'));
+ if (!failures.isEmpty) throw new TestFailure(failures.join('\n'));
+ }), completes);
}
/// Like [startPub], but runs `pub lish` in particular with [server] used both
@@ -612,7 +295,7 @@ ScheduledProcess startPub({List args, Future<Uri> tokenEndpoint}) {
/// package server.
///
/// Any futures in [args] will be resolved before the process is started.
-ScheduledProcess startPubLish(ScheduledServer server, {List args}) {
+ScheduledProcess startPublish(ScheduledServer server, {List args}) {
var tokenEndpoint = server.url.then((url) =>
url.resolve('/token').toString());
if (args == null) args = [];
@@ -626,61 +309,60 @@ ScheduledProcess startPubLish(ScheduledServer server, {List args}) {
void confirmPublish(ScheduledProcess pub) {
// TODO(rnystrom): This is overly specific and inflexible regarding different
// test packages. Should validate this a little more loosely.
- expectLater(pub.nextLine(), equals('Publishing "test_pkg" 1.0.0:'));
- expectLater(pub.nextLine(), equals("|-- LICENSE"));
- expectLater(pub.nextLine(), equals("|-- lib"));
- expectLater(pub.nextLine(), equals("| '-- test_pkg.dart"));
- expectLater(pub.nextLine(), equals("'-- pubspec.yaml"));
- expectLater(pub.nextLine(), equals(""));
+ expect(pub.nextLine(), completion(equals('Publishing "test_pkg" 1.0.0:')));
+ expect(pub.nextLine(), completion(equals("|-- LICENSE")));
+ expect(pub.nextLine(), completion(equals("|-- lib")));
+ expect(pub.nextLine(), completion(equals("| '-- test_pkg.dart")));
+ expect(pub.nextLine(), completion(equals("'-- pubspec.yaml")));
+ expect(pub.nextLine(), completion(equals("")));
pub.writeLine("y");
}
-/// Calls [fn] with appropriately modified arguments to run a pub process. [fn]
-/// should have the same signature as [startProcess], except that the returned
-/// [Future] may have a type other than [Process].
-Future _doPub(Function fn, sandboxDir, List args, Future<Uri> tokenEndpoint) {
+/// Starts a Pub process and returns a [ScheduledProcess] that supports
+/// interaction with that process.
+///
+/// Any futures in [args] will be resolved before the process is started.
+ScheduledProcess startPub({List args, Future<Uri> tokenEndpoint}) {
String pathInSandbox(String relPath) {
return path.join(path.absolute(sandboxDir), relPath);
}
- return defer(() {
- ensureDir(pathInSandbox(appPath));
- return Future.wait([
- _awaitObject(args),
- tokenEndpoint == null ? new Future.immediate(null) : tokenEndpoint
- ]);
- }).then((results) {
- var args = results[0];
- var tokenEndpoint = results[1];
- // Find a Dart executable we can use to spawn. Use the same one that was
- // used to run this script itself.
- var dartBin = new Options().executable;
-
- // If the executable looks like a path, get its full path. That way we
- // can still find it when we spawn it with a different working directory.
- if (dartBin.contains(Platform.pathSeparator)) {
- dartBin = new File(dartBin).fullPathSync();
- }
+ ensureDir(pathInSandbox(appPath));
- // Find the main pub entrypoint.
- var pubPath = fs.joinPaths(testDirectory, '../../pub/pub.dart');
+ // Find a Dart executable we can use to spawn. Use the same one that was
+ // used to run this script itself.
+ var dartBin = new Options().executable;
- var dartArgs = ['--checked', pubPath, '--trace'];
- dartArgs.addAll(args);
+ // If the executable looks like a path, get its full path. That way we
+ // can still find it when we spawn it with a different working directory.
+ if (dartBin.contains(Platform.pathSeparator)) {
+ dartBin = new File(dartBin).fullPathSync();
+ }
+
+ // Find the main pub entrypoint.
+ var pubPath = fs.joinPaths(testDirectory, '../../pub/pub.dart');
- var environment = {
- 'PUB_CACHE': pathInSandbox(cachePath),
- 'DART_SDK': pathInSandbox(sdkPath)
- };
+ var dartArgs = ['--checked', pubPath, '--trace'];
+ dartArgs.addAll(args);
+ if (tokenEndpoint == null) tokenEndpoint = new Future.immediate(null);
+ var optionsFuture = tokenEndpoint.then((tokenEndpoint) {
+ var options = new ProcessOptions();
+ options.workingDirectory = pathInSandbox(appPath);
+ // TODO(nweiz): remove this when issue 9294 is fixed.
+ options.environment = new Map(Platform.environment);
+ options.environment['PUB_CACHE'] = pathInSandbox(cachePath);
+ options.environment['DART_SDK'] = pathInSandbox(sdkPath);
if (tokenEndpoint != null) {
- environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString();
+ options.environment['_PUB_TEST_TOKEN_ENDPOINT'] =
+ tokenEndpoint.toString();
}
-
- return fn(dartBin, dartArgs, workingDir: pathInSandbox(appPath),
- environment: environment);
+ return options;
});
+
+ return new ScheduledProcess.start(dartBin, dartArgs, options: optionsFuture,
+ description: args.isEmpty ? 'pub' : 'pub ${args.first}');
}
/// Skips the current test if Git is not installed. This validates that the
@@ -690,15 +372,13 @@ Future _doPub(Function fn, sandboxDir, List args, Future<Uri> tokenEndpoint) {
/// have git installed to run the tests locally (unless they actually care
/// about the pub git tests).
void ensureGit() {
- _schedule((_) {
+ schedule(() {
return gitlib.isInstalled.then((installed) {
- if (!installed &&
- !Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) {
- _abortScheduled = true;
- }
- return null;
+ if (installed) return;
+ if (Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) return;
+ currentSchedule.abort();
});
- });
+ }, 'ensuring that Git is installed');
}
/// Use [client] as the mock HTTP client for this test.
@@ -708,29 +388,84 @@ void ensureGit() {
void useMockClient(MockClient client) {
var oldInnerClient = httpClient.inner;
httpClient.inner = client;
- _scheduleCleanup((_) {
+ currentSchedule.onComplete.schedule(() {
httpClient.inner = oldInnerClient;
- });
+ }, 'de-activating the mock client');
+}
+
+/// Describes a map representing a library package with the given [name],
+/// [version], and [dependencies].
+Map packageMap(String name, String version, [List dependencies]) {
+ var package = {
+ "name": name,
+ "version": version,
+ "author": "Nathan Weizenbaum <nweiz@google.com>",
+ "homepage": "http://pub.dartlang.org",
+ "description": "A package, I guess."
+ };
+ if (dependencies != null) {
+ package["dependencies"] = dependencyListToMap(dependencies);
+ }
+ return package;
}
-Future _runScheduled(Queue<_ScheduledEvent> scheduled) {
- if (scheduled == null) return new Future.immediate(null);
+/// Describes a map representing a dependency on a package in the package
+/// repository.
+Map dependencyMap(String name, [String versionConstraint]) {
+ var url = port.then((p) => "http://localhost:$p");
+ var dependency = {"hosted": {"name": name, "url": url}};
+ if (versionConstraint != null) dependency["version"] = versionConstraint;
+ return dependency;
+}
- Future runNextEvent(_) {
- if (_abortScheduled || scheduled.isEmpty) {
- _abortScheduled = false;
- return new Future.immediate(null);
- }
+/// Converts a list of dependencies as passed to [package] into a hash as used
+/// in a pubspec.
+Future<Map> dependencyListToMap(List<Map> dependencies) {
+ return awaitObject(dependencies).then((resolvedDependencies) {
+ var result = <String, Map>{};
+ for (var dependency in resolvedDependencies) {
+ var keys = dependency.keys.where((key) => key != "version");
+ var sourceName = only(keys);
+ var source;
+ switch (sourceName) {
+ case "git":
+ source = new GitSource();
+ break;
+ case "hosted":
+ source = new HostedSource();
+ break;
+ case "path":
+ source = new PathSource();
+ break;
+ default:
+ throw 'Unknown source "$sourceName"';
+ }
- var future = scheduled.removeFirst()(_sandboxDir);
- if (future != null) {
- return future.then(runNextEvent);
- } else {
- return runNextEvent(null);
+ result[_packageName(sourceName, dependency[sourceName])] = dependency;
}
- }
+ return result;
+ });
+}
- return runNextEvent(null);
+/// Return the name for the package described by [description] and from
+/// [sourceName].
+String _packageName(String sourceName, description) {
+ switch (sourceName) {
+ case "git":
+ var url = description is String ? description : description['url'];
+ // TODO(rnystrom): Using path.basename on a URL is hacky. If we add URL
+ // support to pkg/pathos, should use an explicit builder for that.
+ return path.basename(url.replaceFirst(new RegExp(r"(\.git)?/?$"), ""));
+ case "hosted":
+ if (description is String) return description;
+ return description['name'];
+ case "path":
+ return path.basename(description);
+ case "sdk":
+ return description;
+ default:
+ return description;
+ }
}
/// Compares the [actual] output from running pub with [expected]. For [String]
@@ -810,392 +545,6 @@ void _validateOutputString(List<String> failures, String pipe,
}
}
-/// Base class for [FileDescriptor] and [DirectoryDescriptor] so that a
-/// directory can contain a heterogeneous collection of files and
-/// subdirectories.
-abstract class Descriptor {
- /// The name of this file or directory. This must be a [String] if the file
- /// or directory is going to be created.
- final Pattern name;
-
- Descriptor(this.name);
-
- /// Creates the file or directory within [dir]. Returns a [Future] that is
- /// completed after the creation is done.
- Future create(dir);
-
- /// Validates that this descriptor correctly matches the corresponding file
- /// system entry within [dir]. Returns a [Future] that completes to `null` if
- /// the entry is valid, or throws an error if it failed.
- Future validate(String dir);
-
- /// Deletes the file or directory within [dir]. Returns a [Future] that is
- /// completed after the deletion is done.
- Future delete(String dir);
-
- /// Loads the file at [path] from within this descriptor. If [path] is empty,
- /// loads the contents of the descriptor itself.
- ByteStream load(List<String> path);
-
- /// Schedules the directory to be created before Pub is run with
- /// [schedulePub]. The directory will be created relative to the sandbox
- /// directory.
- // TODO(nweiz): Use implicit closurization once issue 2984 is fixed.
- void scheduleCreate() => _schedule((dir) => this.create(dir));
-
- /// Schedules the file or directory to be deleted recursively.
- void scheduleDelete() => _schedule((dir) => this.delete(dir));
-
- /// Schedules the directory to be validated after Pub is run with
- /// [schedulePub]. The directory will be validated relative to the sandbox
- /// directory.
- void scheduleValidate() => _schedule((parentDir) => validate(parentDir));
-
- /// Asserts that the name of the descriptor is a [String] and returns it.
- String get _stringName {
- if (name is String) return name;
- throw 'Pattern $name must be a string.';
- }
-
- /// Validates that at least one file in [dir] matching [name] is valid
- /// according to [validate]. [validate] should throw or complete to an
- /// exception if the input path is invalid.
- Future _validateOneMatch(String dir, Future validate(String entry)) {
- // Special-case strings to support multi-level names like "myapp/packages".
- if (name is String) {
- var entry = path.join(dir, name);
- return defer(() {
- if (!entryExists(entry)) {
- throw new TestFailure('Entry $entry not found.');
- }
- return validate(entry);
- });
- }
-
- // TODO(nweiz): remove this when issue 4061 is fixed.
- var stackTrace;
- try {
- throw "";
- } catch (_, localStackTrace) {
- stackTrace = localStackTrace;
- }
-
- return listDir(dir).then((files) {
- var matches = files.where((file) => endsWithPattern(file, name)).toList();
- if (matches.isEmpty) {
- throw new TestFailure('No files in $dir match pattern $name.');
- }
- if (matches.length == 1) return validate(matches[0]);
-
- var failures = [];
- var successes = 0;
- var completer = new Completer();
- checkComplete() {
- if (failures.length + successes != matches.length) return;
- if (successes > 0) {
- completer.complete();
- return;
- }
-
- var error = new StringBuffer();
- error.write("No files named $name in $dir were valid:\n");
- for (var failure in failures) {
- error.write(" $failure\n");
- }
- completer.completeError(
- new TestFailure(error.toString()), stackTrace);
- }
-
- for (var match in matches) {
- var future = validate(match).then((_) {
- successes++;
- checkComplete();
- }).catchError((e) {
- failures.add(e);
- checkComplete();
- });
- }
- return completer.future;
- });
- }
-}
-
-/// Describes a file. These are used both for setting up an expected directory
-/// tree before running a test, and for validating that the file system matches
-/// some expectations after running it.
-class FileDescriptor extends Descriptor {
- /// The contents of the file, in bytes.
- final List<int> contents;
-
- String get textContents => new String.fromCharCodes(contents);
-
- FileDescriptor.bytes(Pattern name, this.contents) : super(name);
-
- FileDescriptor(Pattern name, String contents) :
- this.bytes(name, encodeUtf8(contents));
-
- /// Creates the file within [dir]. Returns a [Future] that is completed after
- /// the creation is done.
- Future<String> create(dir) =>
- defer(() => writeBinaryFile(path.join(dir, _stringName), contents));
-
- /// Deletes the file within [dir]. Returns a [Future] that is completed after
- /// the deletion is done.
- Future delete(dir) =>
- defer(() => deleteFile(path.join(dir, _stringName)));
-
- /// Validates that this file correctly matches the actual file at [path].
- Future validate(String path) {
- return _validateOneMatch(path, (file) {
- var text = readTextFile(file);
- if (text == textContents) return null;
-
- throw new TestFailure(
- 'File $file should contain:\n\n$textContents\n\n'
- 'but contained:\n\n$text');
- });
- }
-
- /// Loads the contents of the file.
- ByteStream load(List<String> path) {
- if (!path.isEmpty) {
- throw "Can't load ${path.join('/')} from within $name: not a directory.";
- }
-
- return new ByteStream.fromBytes(contents);
- }
-}
-
-/// Describes a directory and its contents. These are used both for setting up
-/// an expected directory tree before running a test, and for validating that
-/// the file system matches some expectations after running it.
-class DirectoryDescriptor extends Descriptor {
- /// The files and directories contained in this directory.
- final List<Descriptor> contents;
-
- DirectoryDescriptor(Pattern name, List<Descriptor> contents)
- : this.contents = contents == null ? <Descriptor>[] : contents,
- super(name);
-
- /// Creates the file within [dir]. Returns a [Future] that is completed after
- /// the creation is done.
- Future<String> create(parentDir) {
- return defer(() {
- // Create the directory.
- var dir = ensureDir(path.join(parentDir, _stringName));
- if (contents == null) return dir;
-
- // Recursively create all of its children.
- var childFutures = contents.map((child) => child.create(dir)).toList();
- // Only complete once all of the children have been created too.
- return Future.wait(childFutures).then((_) => dir);
- });
- }
-
- /// Deletes the directory within [dir]. Returns a [Future] that is completed
- /// after the deletion is done.
- Future delete(dir) {
- return deleteDir(path.join(dir, _stringName));
- }
-
- /// Validates that the directory at [path] contains all of the expected
- /// contents in this descriptor. Note that this does *not* check that the
- /// directory doesn't contain other unexpected stuff, just that it *does*
- /// contain the stuff we do expect.
- Future validate(String path) {
- return _validateOneMatch(path, (dir) {
- // Validate each of the items in this directory.
- final entryFutures =
- contents.map((entry) => entry.validate(dir)).toList();
-
- // If they are all valid, the directory is valid.
- return Future.wait(entryFutures).then((entries) => null);
- });
- }
-
- /// Loads [path] from within this directory.
- ByteStream load(List<String> path) {
- if (path.isEmpty) {
- throw "Can't load the contents of $name: is a directory.";
- }
-
- for (var descriptor in contents) {
- if (descriptor.name == path[0]) {
- return descriptor.load(path.sublist(1));
- }
- }
-
- throw "Directory $name doesn't contain ${path.join('/')}.";
- }
-}
-
-/// Wraps a [Future] that will complete to a [Descriptor] and makes it behave
-/// like a concrete [Descriptor]. This is necessary when the contents of the
-/// descriptor depends on information that's not available until part of the
-/// test run is completed.
-class FutureDescriptor extends Descriptor {
- Future<Descriptor> _future;
-
- FutureDescriptor(this._future) : super('<unknown>');
-
- Future create(dir) => _future.then((desc) => desc.create(dir));
-
- Future validate(dir) => _future.then((desc) => desc.validate(dir));
-
- Future delete(dir) => _future.then((desc) => desc.delete(dir));
-
- ByteStream load(List<String> path) {
- var controller = new StreamController<List<int>>();
- _future.then((desc) => store(desc.load(path), controller));
- return new ByteStream(controller.stream);
- }
-}
-
-/// Describes a Git repository and its contents.
-class GitRepoDescriptor extends DirectoryDescriptor {
- GitRepoDescriptor(Pattern name, List<Descriptor> contents)
- : super(name, contents);
-
- /// Creates the Git repository and commits the contents.
- Future create(parentDir) {
- return _runGitCommands(parentDir, [
- ['init'],
- ['add', '.'],
- ['commit', '-m', 'initial commit']
- ]);
- }
-
- /// Commits any changes to the Git repository.
- Future commit(parentDir) {
- return _runGitCommands(parentDir, [
- ['add', '.'],
- ['commit', '-m', 'update']
- ]);
- }
-
- /// Schedules changes to be committed to the Git repository.
- void scheduleCommit() => _schedule((dir) => this.commit(dir));
-
- /// Return a Future that completes to the commit in the git repository
- /// referred to by [ref] at the current point in the scheduled test run.
- Future<String> revParse(String ref) {
- return _scheduleValue((parentDir) {
- return super.create(parentDir).then((rootDir) {
- return _runGit(['rev-parse', ref], rootDir);
- }).then((output) => output[0]);
- });
- }
-
- /// Schedule a Git command to run in this repository.
- void scheduleGit(List<String> args) {
- _schedule((parentDir) => _runGit(args, path.join(parentDir, name)));
- }
-
- Future _runGitCommands(parentDir, List<List<String>> commands) {
- var workingDir;
-
- Future runGitStep(_) {
- if (commands.isEmpty) return new Future.immediate(workingDir);
- var command = commands.removeAt(0);
- return _runGit(command, workingDir).then(runGitStep);
- }
-
- return super.create(parentDir).then((rootDir) {
- workingDir = rootDir;
- return runGitStep(null);
- });
- }
-
- Future<List<String>> _runGit(List<String> args, String workingDir) {
- // Explicitly specify the committer information. Git needs this to commit
- // and we don't want to rely on the buildbots having this already set up.
- var environment = {
- 'GIT_AUTHOR_NAME': 'Pub Test',
- 'GIT_AUTHOR_EMAIL': 'pub@dartlang.org',
- 'GIT_COMMITTER_NAME': 'Pub Test',
- 'GIT_COMMITTER_EMAIL': 'pub@dartlang.org'
- };
-
- return gitlib.run(args, workingDir: workingDir, environment: environment);
- }
-}
-
-/// Describes a gzipped tar file and its contents.
-class TarFileDescriptor extends Descriptor {
- final List<Descriptor> contents;
-
- TarFileDescriptor(Pattern name, this.contents)
- : super(name);
-
- /// Creates the files and directories within this tar file, then archives
- /// them, compresses them, and saves the result to [parentDir].
- Future<String> create(parentDir) {
- return withTempDir((tempDir) {
- return Future.wait(contents.map((child) => child.create(tempDir)))
- .then((createdContents) {
- return createTarGz(createdContents, baseDir: tempDir).toBytes();
- }).then((bytes) {
- var file = path.join(parentDir, _stringName);
- writeBinaryFile(file, bytes);
- return file;
- });
- });
- }
-
- /// Validates that the `.tar.gz` file at [path] contains the expected
- /// contents.
- Future validate(String path) {
- throw "TODO(nweiz): implement this";
- }
-
- Future delete(dir) {
- throw new UnsupportedError('');
- }
-
- /// Loads the contents of this tar file.
- ByteStream load(List<String> path) {
- if (!path.isEmpty) {
- throw "Can't load ${path.join('/')} from within $name: not a directory.";
- }
-
- var controller = new StreamController<List<int>>();
- // TODO(nweiz): propagate any errors to the return value. See issue 3657.
- withTempDir((tempDir) {
- return create(tempDir).then((tar) {
- var sourceStream = new File(tar).openRead();
- return store(sourceStream, controller);
- });
- });
- return new ByteStream(controller.stream);
- }
-}
-
-/// A descriptor that validates that no file or directory exists with the given
-/// name.
-class NothingDescriptor extends Descriptor {
- NothingDescriptor(String name) : super(name);
-
- Future create(dir) => new Future.immediate(null);
- Future delete(dir) => new Future.immediate(null);
-
- Future validate(String dir) {
- return defer(() {
- if (entryExists(path.join(dir, name))) {
- throw new TestFailure('Entry $name in $dir should not exist.');
- }
- });
- }
-
- ByteStream load(List<String> path) {
- if (path.isEmpty) {
- throw "Can't load the contents of $name: it doesn't exist.";
- } else {
- throw "Can't load ${path.join('/')} from within $name: $name doesn't "
- "exist.";
- }
- }
-}
-
/// A function that creates a [Validator] subclass.
typedef Validator ValidatorCreator(Entrypoint entrypoint);
@@ -1203,7 +552,7 @@ typedef Validator ValidatorCreator(Entrypoint entrypoint);
/// Future that contains the errors and warnings produced by that validator.
Future<Pair<List<String>, List<String>>> schedulePackageValidation(
ValidatorCreator fn) {
- return _scheduleValue((sandboxDir) {
+ return schedule(() {
var cache = new SystemCache.withSources(path.join(sandboxDir, cachePath));
return defer(() {
@@ -1212,7 +561,7 @@ Future<Pair<List<String>, List<String>>> schedulePackageValidation(
return new Pair(validator.errors, validator.warnings);
});
});
- });
+ }, "validating package");
}
/// A matcher that matches a Pair.
@@ -1235,406 +584,3 @@ class _PairMatcher extends BaseMatcher {
description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]);
}
}
-
-/// The time (in milliseconds) to wait for scheduled events that could run
-/// forever.
-const _SCHEDULE_TIMEOUT = 10000;
-
-/// A class representing a [Process] that is scheduled to run in the course of
-/// the test. This class allows actions on the process to be scheduled
-/// synchronously. All operations on this class are scheduled.
-///
-/// Before running the test, either [shouldExit] or [kill] must be called on
-/// this to ensure that the process terminates when expected.
-///
-/// If the test fails, this will automatically print out any remaining stdout
-/// and stderr from the process to aid debugging.
-class ScheduledProcess {
- /// The name of the process. Used for error reporting.
- final String name;
-
- /// The process future that's scheduled to run.
- Future<PubProcess> _processFuture;
-
- /// The process that's scheduled to run. It may be null.
- PubProcess _process;
-
- /// The exit code of the scheduled program. It may be null.
- int _exitCode;
-
- /// A future that will complete to a list of all the lines emitted on the
- /// process's standard output stream. This is independent of what data is read
- /// from [_stdout].
- Future<List<String>> _stdoutLines;
-
- /// A [Stream] of stdout lines emitted by the process that's scheduled to run.
- /// It may be null.
- Stream<String> _stdout;
-
- /// A [Future] that will resolve to [_stdout] once it's available.
- Future get _stdoutFuture => _processFuture.then((_) => _stdout);
-
- /// A [StreamSubscription] that controls [_stdout].
- StreamSubscription _stdoutSubscription;
-
- /// A future that will complete to a list of all the lines emitted on the
- /// process's standard error stream. This is independent of what data is read
- /// from [_stderr].
- Future<List<String>> _stderrLines;
-
- /// A [Stream] of stderr lines emitted by the process that's scheduled to run.
- /// It may be null.
- Stream<String> _stderr;
-
- /// A [Future] that will resolve to [_stderr] once it's available.
- Future get _stderrFuture => _processFuture.then((_) => _stderr);
-
- /// A [StreamSubscription] that controls [_stderr].
- StreamSubscription _stderrSubscription;
-
- /// The exit code of the process that's scheduled to run. This will naturally
- /// only complete once the process has terminated.
- Future<int> get _exitCodeFuture => _exitCodeCompleter.future;
-
- /// The completer for [_exitCode].
- final Completer<int> _exitCodeCompleter = new Completer();
-
- /// Whether the user has scheduled the end of this process by calling either
- /// [shouldExit] or [kill].
- bool _endScheduled = false;
-
- /// Whether the process is expected to terminate at this point.
- bool _endExpected = false;
-
- /// Wraps a [Process] [Future] in a scheduled process.
- ScheduledProcess(this.name, Future<PubProcess> process)
- : _processFuture = process {
- var pairFuture = process.then((p) {
- _process = p;
-
- byteStreamToLines(stream) {
- return streamToLines(new ByteStream(stream.handleError((e) {
- registerException(e.error, e.stackTrace);
- })).toStringStream());
- }
-
- var stdoutTee = tee(byteStreamToLines(p.stdout));
- var stdoutPair = streamWithSubscription(stdoutTee.last);
- _stdout = stdoutPair.first;
- _stdoutSubscription = stdoutPair.last;
-
- var stderrTee = tee(byteStreamToLines(p.stderr));
- var stderrPair = streamWithSubscription(stderrTee.last);
- _stderr = stderrPair.first;
- _stderrSubscription = stderrPair.last;
-
- return new Pair(stdoutTee.first, stderrTee.first);
- });
-
- _stdoutLines = pairFuture.then((pair) => pair.first.toList());
- _stderrLines = pairFuture.then((pair) => pair.last.toList());
-
- _schedule((_) {
- if (!_endScheduled) {
- throw new StateError("Scheduled process $name must have shouldExit() "
- "or kill() called before the test is run.");
- }
-
- process.then((p) => p.exitCode).then((exitCode) {
- if (_endExpected) {
- _exitCode = exitCode;
- _exitCodeCompleter.complete(exitCode);
- return;
- }
-
- // Sleep for half a second in case _endExpected is set in the next
- // scheduled event.
- return sleep(500).then((_) {
- if (_endExpected) {
- _exitCodeCompleter.complete(exitCode);
- return;
- }
-
- return _printStreams();
- }).then((_) {
- registerException(new TestFailure("Process $name ended "
- "earlier than scheduled with exit code $exitCode"));
- });
- }).catchError((e) => registerException(e.error, e.stackTrace));
- });
-
- _scheduleOnException((_) {
- if (_process == null) return;
-
- if (_exitCode == null) {
- print("\nKilling process $name prematurely.");
- _endExpected = true;
- _process.kill();
- }
-
- return _printStreams();
- });
-
- _scheduleCleanup((_) {
- if (_process == null) return;
- // Ensure that the process is dead and we aren't waiting on any IO.
- _process.kill();
- _stdoutSubscription.cancel();
- _stderrSubscription.cancel();
- });
- }
-
- /// Reads the next line of stdout from the process.
- Future<String> nextLine() {
- return _scheduleValue((_) {
- return timeout(_stdoutFuture.then((stream) => streamFirst(stream)),
- _SCHEDULE_TIMEOUT,
- "waiting for the next stdout line from process $name");
- });
- }
-
- /// Reads the next line of stderr from the process.
- Future<String> nextErrLine() {
- return _scheduleValue((_) {
- return timeout(_stderrFuture.then((stream) => streamFirst(stream)),
- _SCHEDULE_TIMEOUT,
- "waiting for the next stderr line from process $name");
- });
- }
-
- /// Reads the remaining stdout from the process. This should only be called
- /// after kill() or shouldExit().
- Future<String> remainingStdout() {
- if (!_endScheduled) {
- throw new StateError("remainingStdout() should only be called after "
- "kill() or shouldExit().");
- }
-
- return _scheduleValue((_) {
- return timeout(_stdoutFuture.then((stream) => stream.toList())
- .then((lines) => lines.join("\n")),
- _SCHEDULE_TIMEOUT,
- "waiting for the last stdout line from process $name");
- });
- }
-
- /// Reads the remaining stderr from the process. This should only be called
- /// after kill() or shouldExit().
- Future<String> remainingStderr() {
- if (!_endScheduled) {
- throw new StateError("remainingStderr() should only be called after "
- "kill() or shouldExit().");
- }
-
- return _scheduleValue((_) {
- return timeout(_stderrFuture.then((stream) => stream.toList())
- .then((lines) => lines.join("\n")),
- _SCHEDULE_TIMEOUT,
- "waiting for the last stderr line from process $name");
- });
- }
-
- /// Writes [line] to the process as stdin.
- void writeLine(String line) {
- _schedule((_) => _processFuture.then(
- (p) => p.stdin.add(encodeUtf8('$line\n'))));
- }
-
- /// Kills the process, and waits until it's dead.
- void kill() {
- _endScheduled = true;
- _schedule((_) {
- _endExpected = true;
- _process.kill();
- timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT,
- "waiting for process $name to die");
- });
- }
-
- /// Waits for the process to exit, and verifies that the exit code matches
- /// [expectedExitCode] (if given).
- void shouldExit([int expectedExitCode]) {
- _endScheduled = true;
- _schedule((_) {
- _endExpected = true;
- return timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT,
- "waiting for process $name to exit").then((exitCode) {
- if (expectedExitCode != null) {
- expect(exitCode, equals(expectedExitCode));
- }
- });
- });
- }
-
- /// Prints the remaining data in the process's stdout and stderr streams.
- /// Prints nothing if the streams are empty.
- Future _printStreams() {
- void printStream(String streamName, List<String> lines) {
- if (lines.isEmpty) return;
-
- print('\nProcess $name $streamName:');
- for (var line in lines) {
- print('| $line');
- }
- }
-
- return _stdoutLines.then((stdoutLines) {
- printStream('stdout', stdoutLines);
- return _stderrLines.then((stderrLines) {
- printStream('stderr', stderrLines);
- });
- });
- }
-}
-
-/// A class representing an [HttpServer] that's scheduled to run in the course
-/// of the test. This class allows the server's request handling to be
-/// scheduled synchronously. All operations on this class are scheduled.
-class ScheduledServer {
- /// The wrapped server.
- final Future<HttpServer> _server;
-
- /// The queue of handlers to run for upcoming requests.
- final _handlers = new Queue<Future>();
-
- /// The requests to be ignored.
- final _ignored = new Set<Pair<String, String>>();
-
- ScheduledServer._(this._server);
-
- /// Creates a new server listening on an automatically-allocated port on
- /// localhost.
- factory ScheduledServer() {
- var scheduledServer;
- scheduledServer = new ScheduledServer._(_scheduleValue((_) {
- return SafeHttpServer.bind("127.0.0.1", 0).then((server) {
- server.listen(scheduledServer._awaitHandle);
- _scheduleCleanup((_) => server.close());
- return server;
- });
- }));
- return scheduledServer;
- }
-
- /// The port on which the server is listening.
- Future<int> get port => _server.then((s) => s.port);
-
- /// The base URL of the server, including its port.
- Future<Uri> get url =>
- port.then((p) => Uri.parse("http://localhost:$p"));
-
- /// Assert that the next request has the given [method] and [path], and pass
- /// it to [handler] to handle. If [handler] returns a [Future], wait until
- /// it's completed to continue the schedule.
- void handle(String method, String path,
- Future handler(HttpRequest request, HttpResponse response)) {
- var handlerCompleter = new Completer<Function>();
- _scheduleValue((_) {
- var requestCompleteCompleter = new Completer();
- handlerCompleter.complete((request, response) {
- expect(request.method, equals(method));
- expect(request.uri.path, equals(path));
-
- var future = handler(request, response);
- if (future == null) future = new Future.immediate(null);
- chainToCompleter(future, requestCompleteCompleter);
- });
- return timeout(requestCompleteCompleter.future,
- _SCHEDULE_TIMEOUT, "waiting for $method $path");
- });
- _handlers.add(handlerCompleter.future);
- }
-
- /// Ignore all requests with the given [method] and [path]. If one is
- /// received, don't respond to it.
- void ignore(String method, String path) =>
- _ignored.add(new Pair(method, path));
-
- /// Raises an error complaining of an unexpected request.
- void _awaitHandle(HttpRequest request) {
- HttpResponse response = request.response;
- if (_ignored.contains(new Pair(request.method, request.uri.path))) return;
- var future = timeout(defer(() {
- if (_handlers.isEmpty) {
- fail('Unexpected ${request.method} request to ${request.uri.path}.');
- }
- return _handlers.removeFirst();
- }).then((handler) {
- handler(request, response);
- }), _SCHEDULE_TIMEOUT, "waiting for a handler for ${request.method} "
- "${request.uri.path}");
- expect(future, completes);
- }
-}
-
-/// Takes a simple data structure (composed of [Map]s, [List]s, scalar objects,
-/// and [Future]s) and recursively resolves all the [Future]s contained within.
-/// Completes with the fully resolved structure.
-Future _awaitObject(object) {
- // Unroll nested futures.
- if (object is Future) return object.then(_awaitObject);
- if (object is Collection) {
- return Future.wait(object.map(_awaitObject).toList());
- }
- if (object is! Map) return new Future.immediate(object);
-
- var pairs = <Future<Pair>>[];
- object.forEach((key, value) {
- pairs.add(_awaitObject(value)
- .then((resolved) => new Pair(key, resolved)));
- });
- return Future.wait(pairs).then((resolvedPairs) {
- var map = {};
- for (var pair in resolvedPairs) {
- map[pair.first] = pair.last;
- }
- return map;
- });
-}
-
-/// Schedules a callback to be called as part of the test case.
-void _schedule(_ScheduledEvent event) {
- if (_scheduled == null) _scheduled = new Queue();
- _scheduled.addLast(event);
-}
-
-/// Like [_schedule], but pipes the return value of [event] to a returned
-/// [Future].
-Future _scheduleValue(_ScheduledEvent event) {
- var completer = new Completer();
- _schedule((parentDir) {
- chainToCompleter(event(parentDir), completer);
- return completer.future;
- });
- return completer.future;
-}
-
-/// Schedules a callback to be called after the test case has completed, even
-/// if it failed.
-void _scheduleCleanup(_ScheduledEvent event) {
- if (_scheduledCleanup == null) _scheduledCleanup = new Queue();
- _scheduledCleanup.addLast(event);
-}
-
-/// Schedules a callback to be called after the test case has completed, but
-/// only if it failed.
-void _scheduleOnException(_ScheduledEvent event) {
- if (_scheduledOnException == null) _scheduledOnException = new Queue();
- _scheduledOnException.addLast(event);
-}
-
-/// Like [expect], but for [Future]s that complete as part of the scheduled
-/// test. This is necessary to ensure that the exception thrown by the
-/// expectation failing is handled by the scheduler.
-///
-/// Note that [matcher] matches against the completed value of [actual], so
-/// calling [completion] is unnecessary.
-void expectLater(Future actual, matcher, {String reason,
- FailureHandler failureHandler, bool verbose: false}) {
- _schedule((_) {
- return actual.then((value) {
- expect(value, matcher, reason: reason, failureHandler: failureHandler,
- verbose: false);
- });
- });
-}
« no previous file with comments | « utils/tests/pub/sdk_constraint_test.dart ('k') | utils/tests/pub/update/git/do_not_update_if_unneeded_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698