| Index: sdk/lib/_internal/pub_generated/test/test_pub.dart
|
| diff --git a/sdk/lib/_internal/pub/test/test_pub.dart b/sdk/lib/_internal/pub_generated/test/test_pub.dart
|
| similarity index 83%
|
| copy from sdk/lib/_internal/pub/test/test_pub.dart
|
| copy to sdk/lib/_internal/pub_generated/test/test_pub.dart
|
| index cb555a085dcc4f7dd484efd02c065b5c66f84928..1fcc8d67aece400ab78239f1327d36679220200b 100644
|
| --- a/sdk/lib/_internal/pub/test/test_pub.dart
|
| +++ b/sdk/lib/_internal/pub_generated/test/test_pub.dart
|
| @@ -142,8 +142,8 @@ void withBarbackVersions(String versionConstraint, void callback()) {
|
| _barbackDeps.forEach((constraint, deps) {
|
| if (!constraint.allows(version)) return;
|
| deps.forEach((packageName, version) {
|
| - _packageOverrides[packageName] = p.join(
|
| - repoRoot, 'third_party', 'pkg', '$packageName-$version');
|
| + _packageOverrides[packageName] =
|
| + p.join(repoRoot, 'third_party', 'pkg', '$packageName-$version');
|
| });
|
| });
|
|
|
| @@ -196,9 +196,9 @@ void serve([List<d.Descriptor> contents]) {
|
| var path = p.posix.fromUri(request.url.path.replaceFirst("/", ""));
|
| _requestedPaths.add(path);
|
|
|
| - return validateStream(baseDir.load(path))
|
| - .then((stream) => new shelf.Response.ok(stream))
|
| - .catchError((error) {
|
| + return validateStream(
|
| + baseDir.load(
|
| + path)).then((stream) => new shelf.Response.ok(stream)).catchError((error) {
|
| return new shelf.Response.notFound('File "$path" not found.');
|
| });
|
| }, 'localhost', 0).then((server) {
|
| @@ -250,12 +250,15 @@ bool _abortScheduled = false;
|
| /// Enum identifying a pub command that can be run with a well-defined success
|
| /// output.
|
| class RunCommand {
|
| - static final get = new RunCommand('get', new RegExp(
|
| - r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
|
| - static final upgrade = new RunCommand('upgrade', new RegExp(
|
| - r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
|
| - static final downgrade = new RunCommand('downgrade', new RegExp(
|
| - r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
|
| + static final get = new RunCommand(
|
| + 'get',
|
| + new RegExp(r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
|
| + static final upgrade = new RunCommand(
|
| + 'upgrade',
|
| + new RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
|
| + static final downgrade = new RunCommand(
|
| + 'downgrade',
|
| + new RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
|
|
|
| final String name;
|
| final RegExp success;
|
| @@ -285,8 +288,8 @@ void forBothPubGetAndUpgrade(void callback(RunCommand command)) {
|
| ///
|
| /// If [exitCode] is given, expects the command to exit with that code.
|
| // TODO(rnystrom): Clean up other tests to call this when possible.
|
| -void pubCommand(RunCommand command,
|
| - {Iterable<String> args, output, error, warning, int exitCode}) {
|
| +void pubCommand(RunCommand command, {Iterable<String> args, output, error,
|
| + warning, int exitCode}) {
|
| if (error != null && warning != null) {
|
| throw new ArgumentError("Cannot pass both 'error' and 'warning'.");
|
| }
|
| @@ -306,19 +309,34 @@ void pubCommand(RunCommand command,
|
| }
|
|
|
| void pubGet({Iterable<String> args, output, error, warning, int exitCode}) {
|
| - pubCommand(RunCommand.get, args: args, output: output, error: error,
|
| - warning: warning, exitCode: exitCode);
|
| + pubCommand(
|
| + RunCommand.get,
|
| + args: args,
|
| + output: output,
|
| + error: error,
|
| + warning: warning,
|
| + exitCode: exitCode);
|
| }
|
|
|
| void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) {
|
| - pubCommand(RunCommand.upgrade, args: args, output: output, error: error,
|
| - warning: warning, exitCode: exitCode);
|
| -}
|
| -
|
| -void pubDowngrade({Iterable<String> args, output, error, warning,
|
| - int exitCode}) {
|
| - pubCommand(RunCommand.downgrade, args: args, output: output, error: error,
|
| - warning: warning, exitCode: exitCode);
|
| + pubCommand(
|
| + RunCommand.upgrade,
|
| + args: args,
|
| + output: output,
|
| + error: error,
|
| + warning: warning,
|
| + exitCode: exitCode);
|
| +}
|
| +
|
| +void pubDowngrade({Iterable<String> args, output, error, warning, int exitCode})
|
| + {
|
| + pubCommand(
|
| + RunCommand.downgrade,
|
| + args: args,
|
| + output: output,
|
| + error: error,
|
| + warning: warning,
|
| + exitCode: exitCode);
|
| }
|
|
|
| /// Schedules starting the "pub [global] run" process and validates the
|
| @@ -345,11 +363,11 @@ ScheduledProcess pubRun({bool global: false, Iterable<String> args}) {
|
| /// The [body] should schedule a series of operations which will be run
|
| /// asynchronously.
|
| void integration(String description, void body()) =>
|
| - _integration(description, body, test);
|
| + _integration(description, body, test);
|
|
|
| /// Like [integration], but causes only this test to run.
|
| void solo_integration(String description, void body()) =>
|
| - _integration(description, body, solo_test);
|
| + _integration(description, body, solo_test);
|
|
|
| void _integration(String description, void body(), [Function testFn]) {
|
| testFn(description, () {
|
| @@ -363,7 +381,8 @@ void _integration(String description, void body(), [Function testFn]) {
|
|
|
| _sandboxDir = createSystemTempDir();
|
| d.defaultRoot = sandboxDir;
|
| - currentSchedule.onComplete.schedule(() => deleteEntry(_sandboxDir),
|
| + currentSchedule.onComplete.schedule(
|
| + () => deleteEntry(_sandboxDir),
|
| 'deleting the sandbox directory');
|
|
|
| // Schedule the test.
|
| @@ -373,16 +392,13 @@ void _integration(String description, void body(), [Function testFn]) {
|
|
|
| /// Get the path to the root "pub/test" directory containing the pub
|
| /// tests.
|
| -String get testDirectory =>
|
| - p.absolute(p.dirname(libraryPath('test_pub')));
|
| +String get testDirectory => p.absolute(p.dirname(libraryPath('test_pub')));
|
|
|
| /// 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(
|
| - () => renameDir(
|
| - p.join(sandboxDir, from),
|
| - p.join(sandboxDir, to)),
|
| + () => renameDir(p.join(sandboxDir, from), p.join(sandboxDir, to)),
|
| 'renaming $from to $to');
|
| }
|
|
|
| @@ -390,9 +406,7 @@ void scheduleRename(String from, String to) {
|
| /// both of which are assumed to be relative to [sandboxDir].
|
| void scheduleSymlink(String target, String symlink) {
|
| schedule(
|
| - () => createSymlink(
|
| - p.join(sandboxDir, target),
|
| - p.join(sandboxDir, symlink)),
|
| + () => createSymlink(p.join(sandboxDir, target), p.join(sandboxDir, symlink)),
|
| 'symlinking $target to $symlink');
|
| }
|
|
|
| @@ -409,8 +423,8 @@ void scheduleSymlink(String target, String symlink) {
|
| ///
|
| /// If [environment] is given, any keys in it will override the environment
|
| /// variables passed to the spawned process.
|
| -void schedulePub({List args, output, error, outputJson,
|
| - int exitCode: exit_codes.SUCCESS, Map<String, String> environment}) {
|
| +void schedulePub({List args, output, error, outputJson, int exitCode:
|
| + exit_codes.SUCCESS, Map<String, String> environment}) {
|
| // Cannot pass both output and outputJson.
|
| assert(output == null || outputJson == null);
|
|
|
| @@ -420,10 +434,9 @@ void schedulePub({List args, output, error, outputJson,
|
| var failures = [];
|
| var stderr;
|
|
|
| - expect(Future.wait([
|
| - pub.stdoutStream().toList(),
|
| - pub.stderrStream().toList()
|
| - ]).then((results) {
|
| + expect(
|
| + Future.wait(
|
| + [pub.stdoutStream().toList(), pub.stderrStream().toList()]).then((results) {
|
| var stdout = results[0].join("\n");
|
| stderr = results[1].join("\n");
|
|
|
| @@ -449,8 +462,8 @@ void schedulePub({List args, output, error, outputJson,
|
| ///
|
| /// Any futures in [args] will be resolved before the process is started.
|
| ScheduledProcess startPublish(ScheduledServer server, {List args}) {
|
| - var tokenEndpoint = server.url.then((url) =>
|
| - url.resolve('/token').toString());
|
| + var tokenEndpoint =
|
| + server.url.then((url) => url.resolve('/token').toString());
|
| if (args == null) args = [];
|
| args = flatten(['lish', '--server', tokenEndpoint, args]);
|
| return startPub(args: args, tokenEndpoint: tokenEndpoint);
|
| @@ -464,13 +477,10 @@ void confirmPublish(ScheduledProcess pub) {
|
| // TODO(rnystrom): This is overly specific and inflexible regarding different
|
| // test packages. Should validate this a little more loosely.
|
| pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to '));
|
| - pub.stdout.expect(emitsLines(
|
| - "|-- LICENSE\n"
|
| - "|-- lib\n"
|
| - "| '-- test_pkg.dart\n"
|
| - "'-- pubspec.yaml\n"
|
| - "\n"
|
| - "Looks great! Are you ready to upload your package (y/n)?"));
|
| + pub.stdout.expect(
|
| + emitsLines(
|
| + "|-- LICENSE\n" "|-- lib\n" "| '-- test_pkg.dart\n" "'-- pubspec.yaml\n" "\n"
|
| + "Looks great! Are you ready to upload your package (y/n)?"));
|
| pub.writeLine("y");
|
| }
|
|
|
| @@ -481,26 +491,38 @@ String _pathInSandbox(String relPath) {
|
| }
|
|
|
| /// Gets the environment variables used to run pub in a test context.
|
| -Future<Map> getPubTestEnvironment([String tokenEndpoint]) async {
|
| - var environment = {};
|
| - environment['_PUB_TESTING'] = 'true';
|
| - environment['PUB_CACHE'] = _pathInSandbox(cachePath);
|
| -
|
| - // Ensure a known SDK version is set for the tests that rely on that.
|
| - environment['_PUB_TEST_SDK_VERSION'] = "0.1.2+3";
|
| -
|
| - if (tokenEndpoint != null) {
|
| - environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString();
|
| - }
|
| -
|
| - if (_hasServer) {
|
| - return port.then((p) {
|
| - environment['PUB_HOSTED_URL'] = "http://localhost:$p";
|
| - return environment;
|
| - });
|
| - }
|
| -
|
| - return environment;
|
| +Future<Map> getPubTestEnvironment([String tokenEndpoint]) {
|
| + final completer0 = new Completer();
|
| + scheduleMicrotask(() {
|
| + try {
|
| + var environment = {};
|
| + environment['_PUB_TESTING'] = 'true';
|
| + environment['PUB_CACHE'] = _pathInSandbox(cachePath);
|
| + environment['_PUB_TEST_SDK_VERSION'] = "0.1.2+3";
|
| + join0() {
|
| + join1() {
|
| + completer0.complete(environment);
|
| + }
|
| + if (_hasServer) {
|
| + completer0.complete(port.then(((p) {
|
| + environment['PUB_HOSTED_URL'] = "http://localhost:$p";
|
| + return environment;
|
| + })));
|
| + } else {
|
| + join1();
|
| + }
|
| + }
|
| + if (tokenEndpoint != null) {
|
| + environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString();
|
| + join0();
|
| + } else {
|
| + join0();
|
| + }
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| + }
|
| + });
|
| + return completer0.future;
|
| }
|
|
|
| /// Starts a Pub process and returns a [ScheduledProcess] that supports
|
| @@ -510,8 +532,8 @@ Future<Map> getPubTestEnvironment([String tokenEndpoint]) async {
|
| ///
|
| /// If [environment] is given, any keys in it will override the environment
|
| /// variables passed to the spawned process.
|
| -ScheduledProcess startPub({List args, Future<String> tokenEndpoint,
|
| - Map<String, String> environment}) {
|
| +ScheduledProcess startPub({List args, Future<String> tokenEndpoint, Map<String,
|
| + String> environment}) {
|
| ensureDir(_pathInSandbox(appPath));
|
|
|
| // Find a Dart executable we can use to spawn. Use the same one that was
|
| @@ -536,14 +558,16 @@ ScheduledProcess startPub({List args, Future<String> tokenEndpoint,
|
| dartArgs.addAll(args);
|
|
|
| if (tokenEndpoint == null) tokenEndpoint = new Future.value();
|
| - var environmentFuture = tokenEndpoint
|
| - .then((tokenEndpoint) => getPubTestEnvironment(tokenEndpoint))
|
| - .then((pubEnvironment) {
|
| + var environmentFuture = tokenEndpoint.then(
|
| + (tokenEndpoint) => getPubTestEnvironment(tokenEndpoint)).then((pubEnvironment) {
|
| if (environment != null) pubEnvironment.addAll(environment);
|
| return pubEnvironment;
|
| });
|
|
|
| - return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture,
|
| + return new PubProcess.start(
|
| + dartBin,
|
| + dartArgs,
|
| + environment: environmentFuture,
|
| workingDirectory: _pathInSandbox(appPath),
|
| description: args.isEmpty ? 'pub' : 'pub ${args.first}');
|
| }
|
| @@ -556,20 +580,21 @@ class PubProcess extends ScheduledProcess {
|
| Stream<String> _stdout;
|
| Stream<String> _stderr;
|
|
|
| - PubProcess.start(executable, arguments,
|
| - {workingDirectory, environment, String description,
|
| - Encoding encoding: UTF8})
|
| - : super.start(executable, arguments,
|
| - workingDirectory: workingDirectory,
|
| - environment: environment,
|
| - description: description,
|
| - encoding: encoding);
|
| + PubProcess.start(executable, arguments, {workingDirectory, environment,
|
| + String description, Encoding encoding: UTF8})
|
| + : super.start(
|
| + executable,
|
| + arguments,
|
| + workingDirectory: workingDirectory,
|
| + environment: environment,
|
| + description: description,
|
| + encoding: encoding);
|
|
|
| Stream<Pair<log.Level, String>> _logStream() {
|
| if (_log == null) {
|
| _log = mergeStreams(
|
| - _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
|
| - _outputToLog(super.stderrStream(), log.Level.ERROR));
|
| + _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
|
| + _outputToLog(super.stderrStream(), log.Level.ERROR));
|
| }
|
|
|
| var pair = tee(_log);
|
| @@ -579,9 +604,12 @@ class PubProcess extends ScheduledProcess {
|
|
|
| final _logLineRegExp = new RegExp(r"^([A-Z ]{4})[:|] (.*)$");
|
| final _logLevels = [
|
| - log.Level.ERROR, log.Level.WARNING, log.Level.MESSAGE, log.Level.IO,
|
| - log.Level.SOLVER, log.Level.FINE
|
| - ].fold(<String, log.Level>{}, (levels, level) {
|
| + log.Level.ERROR,
|
| + log.Level.WARNING,
|
| + log.Level.MESSAGE,
|
| + log.Level.IO,
|
| + log.Level.SOLVER,
|
| + log.Level.FINE].fold(<String, log.Level>{}, (levels, level) {
|
| levels[level.name] = level;
|
| return levels;
|
| });
|
| @@ -658,21 +686,19 @@ void ensureGit() {
|
| /// server doesn't serve barback. The other parameters here follow
|
| /// [createLockFile].
|
| void makeGlobalPackage(String package, String version,
|
| - Iterable<d.Descriptor> contents, {Iterable<String> pkg,
|
| - Map<String, String> hosted}) {
|
| + Iterable<d.Descriptor> contents, {Iterable<String> pkg, Map<String,
|
| + String> hosted}) {
|
| // Start the server so we know what port to use in the cache directory name.
|
| serveNoPackages();
|
|
|
| // Create the package in the hosted cache.
|
| - d.hostedCache([
|
| - d.dir("$package-$version", contents)
|
| - ]).create();
|
| + d.hostedCache([d.dir("$package-$version", contents)]).create();
|
|
|
| var lockFile = _createLockFile(pkg: pkg, hosted: hosted);
|
|
|
| // Add the root package to the lockfile.
|
| - var id = new PackageId(package, "hosted", new Version.parse(version),
|
| - package);
|
| + var id =
|
| + new PackageId(package, "hosted", new Version.parse(version), package);
|
| lockFile.packages[package] = id;
|
|
|
| // Write the lockfile to the global cache.
|
| @@ -680,11 +706,12 @@ void makeGlobalPackage(String package, String version,
|
| sources.register(new HostedSource());
|
| sources.register(new PathSource());
|
|
|
| - d.dir(cachePath, [
|
| - d.dir("global_packages", [
|
| - d.file("$package.lock", lockFile.serialize(null, sources))
|
| - ])
|
| - ]).create();
|
| + d.dir(
|
| + cachePath,
|
| + [
|
| + d.dir(
|
| + "global_packages",
|
| + [d.file("$package.lock", lockFile.serialize(null, sources))])]).create();
|
| }
|
|
|
| /// Creates a lock file for [package] without running `pub get`.
|
| @@ -704,7 +731,8 @@ void createLockFile(String package, {Iterable<String> sandbox,
|
| sources.register(new HostedSource());
|
| sources.register(new PathSource());
|
|
|
| - d.file(p.join(package, 'pubspec.lock'),
|
| + d.file(
|
| + p.join(package, 'pubspec.lock'),
|
| lockFile.serialize(null, sources)).create();
|
| }
|
|
|
| @@ -717,8 +745,8 @@ void createLockFile(String package, {Iterable<String> sandbox,
|
| ///
|
| /// [hosted] is a list of package names to version strings for dependencies on
|
| /// hosted packages.
|
| -LockFile _createLockFile({Iterable<String> sandbox,
|
| -Iterable<String> pkg, Map<String, String> hosted}) {
|
| +LockFile _createLockFile({Iterable<String> sandbox, Iterable<String> pkg,
|
| + Map<String, String> hosted}) {
|
| var dependencies = {};
|
|
|
| if (sandbox != null) {
|
| @@ -733,9 +761,9 @@ Iterable<String> pkg, Map<String, String> hosted}) {
|
|
|
| var path;
|
| if (package == 'barback' && _packageOverrides == null) {
|
| - throw new StateError("createLockFile() can only create a lock file "
|
| - "with a barback dependency within a withBarbackVersions() "
|
| - "block.");
|
| + throw new StateError(
|
| + "createLockFile() can only create a lock file "
|
| + "with a barback dependency within a withBarbackVersions() " "block.");
|
| }
|
|
|
| if (_packageOverrides.containsKey(package)) {
|
| @@ -745,8 +773,7 @@ Iterable<String> pkg, Map<String, String> hosted}) {
|
| }
|
|
|
| dependencies[package] = path;
|
| - var pubspec = loadYaml(
|
| - readTextFile(p.join(path, 'pubspec.yaml')));
|
| + var pubspec = loadYaml(readTextFile(p.join(path, 'pubspec.yaml')));
|
| var packageDeps = pubspec['dependencies'];
|
| if (packageDeps == null) return;
|
| packageDeps.keys.forEach(_addPackage);
|
| @@ -809,8 +836,16 @@ Map packageMap(String name, String version, [Map dependencies]) {
|
| }
|
|
|
| /// Resolves [target] relative to the path to pub's `test/asset` directory.
|
| -String testAssetPath(String target) =>
|
| - p.join(p.dirname(libraryPath('test_pub')), 'asset', target);
|
| +String testAssetPath(String target) {
|
| + var libPath = libraryPath('test_pub');
|
| +
|
| + // We are running from the generated directory, but non-dart assets are only
|
| + // in the canonical directory.
|
| + // TODO(rnystrom): Remove this when #104 is fixed.
|
| + libPath = libPath.replaceAll('pub_generated', 'pub');
|
| +
|
| + return p.join(p.dirname(libPath), 'asset', target);
|
| +}
|
|
|
| /// Returns a Map in the format used by the pub.dartlang.org API to represent a
|
| /// package version.
|
| @@ -826,8 +861,7 @@ Map packageVersionApiMap(Map pubspec, {bool full: false}) {
|
| 'version': version,
|
| 'url': '/api/packages/$name/versions/$version',
|
| 'archive_url': '/packages/$name/versions/$version.tar.gz',
|
| - 'new_dartdoc_url': '/api/packages/$name/versions/$version'
|
| - '/new_dartdoc',
|
| + 'new_dartdoc_url': '/api/packages/$name/versions/$version' '/new_dartdoc',
|
| 'package_url': '/api/packages/$name'
|
| };
|
|
|
| @@ -855,7 +889,7 @@ String binStubName(String name) => Platform.isWindows ? '$name.bat' : name;
|
| ///
|
| /// If it's a [RegExp] or [Matcher], just reports whether the output matches.
|
| void _validateOutput(List<String> failures, String pipe, expected,
|
| - String actual) {
|
| + String actual) {
|
| if (expected == null) return;
|
|
|
| if (expected is String) {
|
| @@ -866,8 +900,8 @@ void _validateOutput(List<String> failures, String pipe, expected,
|
| }
|
| }
|
|
|
| -void _validateOutputString(List<String> failures, String pipe,
|
| - String expected, String actual) {
|
| +void _validateOutputString(List<String> failures, String pipe, String expected,
|
| + String actual) {
|
| var actualLines = actual.split("\n");
|
| var expectedLines = expected.split("\n");
|
|
|
| @@ -918,12 +952,12 @@ void _validateOutputString(List<String> failures, String pipe,
|
|
|
| /// Validates that [actualText] is a string of JSON that matches [expected],
|
| /// which may be a literal JSON object, or any other [Matcher].
|
| -void _validateOutputJson(List<String> failures, String pipe,
|
| - expected, String actualText) {
|
| +void _validateOutputJson(List<String> failures, String pipe, expected,
|
| + String actualText) {
|
| var actual;
|
| try {
|
| actual = JSON.decode(actualText);
|
| - } on FormatException catch(error) {
|
| + } on FormatException catch (error) {
|
| failures.add('Expected $pipe JSON:');
|
| failures.add(expected);
|
| failures.add('Got invalid JSON:');
|
| @@ -941,11 +975,11 @@ typedef Validator ValidatorCreator(Entrypoint entrypoint);
|
| ///
|
| /// Returns a scheduled Future that contains the errors and warnings produced
|
| /// by that validator.
|
| -Future<Pair<List<String>, List<String>>> schedulePackageValidation(
|
| - ValidatorCreator fn) {
|
| +Future<Pair<List<String>, List<String>>>
|
| + schedulePackageValidation(ValidatorCreator fn) {
|
| return schedule(() {
|
| - var cache = new SystemCache.withSources(
|
| - rootDir: p.join(sandboxDir, cachePath));
|
| + var cache =
|
| + new SystemCache.withSources(rootDir: p.join(sandboxDir, cachePath));
|
|
|
| return new Future.sync(() {
|
| var validator = fn(new Entrypoint(p.join(sandboxDir, appPath), cache));
|
| @@ -958,7 +992,7 @@ Future<Pair<List<String>, List<String>>> schedulePackageValidation(
|
|
|
| /// A matcher that matches a Pair.
|
| Matcher pairOf(Matcher firstMatcher, Matcher lastMatcher) =>
|
| - new _PairMatcher(firstMatcher, lastMatcher);
|
| + new _PairMatcher(firstMatcher, lastMatcher);
|
|
|
| class _PairMatcher extends Matcher {
|
| final Matcher _firstMatcher;
|
|
|