OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub | 5 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub |
6 /// tests are integration tests that stage some stuff on the file system, run | 6 /// tests are integration tests that stage some stuff on the file system, run |
7 /// pub, and then validate the results. This library provides an API to build | 7 /// pub, and then validate the results. This library provides an API to build |
8 /// tests like that. | 8 /// tests like that. |
9 library test_pub; | 9 library test_pub; |
10 | 10 |
11 import 'dart:async'; | 11 import 'dart:async'; |
12 import 'dart:convert'; | 12 import 'dart:convert'; |
13 import 'dart:io'; | 13 import 'dart:io'; |
14 import 'dart:math'; | 14 import 'dart:math'; |
15 | 15 |
16 import 'package:http/testing.dart'; | 16 import 'package:http/testing.dart'; |
17 import 'package:path/path.dart' as path; | 17 import 'package:path/path.dart' as path; |
18 import 'package:scheduled_test/scheduled_process.dart'; | 18 import 'package:scheduled_test/scheduled_process.dart'; |
19 import 'package:scheduled_test/scheduled_server.dart'; | 19 import 'package:scheduled_test/scheduled_server.dart'; |
| 20 import 'package:scheduled_test/scheduled_stream.dart'; |
20 import 'package:scheduled_test/scheduled_test.dart'; | 21 import 'package:scheduled_test/scheduled_test.dart'; |
21 import 'package:unittest/compact_vm_config.dart'; | 22 import 'package:unittest/compact_vm_config.dart'; |
22 import 'package:yaml/yaml.dart'; | 23 import 'package:yaml/yaml.dart'; |
23 | 24 |
24 import '../lib/src/entrypoint.dart'; | 25 import '../lib/src/entrypoint.dart'; |
25 import '../lib/src/exit_codes.dart' as exit_codes; | 26 import '../lib/src/exit_codes.dart' as exit_codes; |
26 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides | 27 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides |
27 // with the git descriptor method. Maybe we should try to clean up the top level | 28 // with the git descriptor method. Maybe we should try to clean up the top level |
28 // scope a bit? | 29 // scope a bit? |
29 import '../lib/src/git.dart' as gitlib; | 30 import '../lib/src/git.dart' as gitlib; |
(...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
387 // Cannot pass both output and outputJson. | 388 // Cannot pass both output and outputJson. |
388 assert(output == null || outputJson == null); | 389 assert(output == null || outputJson == null); |
389 | 390 |
390 var pub = startPub(args: args, tokenEndpoint: tokenEndpoint); | 391 var pub = startPub(args: args, tokenEndpoint: tokenEndpoint); |
391 pub.shouldExit(exitCode); | 392 pub.shouldExit(exitCode); |
392 | 393 |
393 var failures = []; | 394 var failures = []; |
394 var stderr; | 395 var stderr; |
395 | 396 |
396 expect(Future.wait([ | 397 expect(Future.wait([ |
397 pub.remainingStdout(), | 398 pub.stdoutStream().toList(), |
398 pub.remainingStderr() | 399 pub.stderrStream().toList() |
399 ]).then((results) { | 400 ]).then((results) { |
400 stderr = results[1]; | 401 var stdout = results[0].join("\n"); |
| 402 stderr = results[1].join("\n"); |
401 | 403 |
402 if (outputJson == null) { | 404 if (outputJson == null) { |
403 _validateOutput(failures, 'stdout', output, results[0]); | 405 _validateOutput(failures, 'stdout', output, stdout); |
404 return null; | 406 return null; |
405 } | 407 } |
406 | 408 |
407 // Allow the expected JSON to contain futures. | 409 // Allow the expected JSON to contain futures. |
408 return awaitObject(outputJson).then((resolved) { | 410 return awaitObject(outputJson).then((resolved) { |
409 _validateOutputJson(failures, 'stdout', resolved, results[0]); | 411 _validateOutputJson(failures, 'stdout', resolved, stdout); |
410 }); | 412 }); |
411 }).then((_) { | 413 }).then((_) { |
412 _validateOutput(failures, 'stderr', error, stderr); | 414 _validateOutput(failures, 'stderr', error, stderr); |
413 | 415 |
414 if (!failures.isEmpty) throw new TestFailure(failures.join('\n')); | 416 if (!failures.isEmpty) throw new TestFailure(failures.join('\n')); |
415 }), completes); | 417 }), completes); |
416 } | 418 } |
417 | 419 |
418 /// Like [startPub], but runs `pub lish` in particular with [server] used both | 420 /// Like [startPub], but runs `pub lish` in particular with [server] used both |
419 /// as the OAuth2 server (with "/token" as the token endpoint) and as the | 421 /// as the OAuth2 server (with "/token" as the token endpoint) and as the |
420 /// package server. | 422 /// package server. |
421 /// | 423 /// |
422 /// Any futures in [args] will be resolved before the process is started. | 424 /// Any futures in [args] will be resolved before the process is started. |
423 ScheduledProcess startPublish(ScheduledServer server, {List args}) { | 425 ScheduledProcess startPublish(ScheduledServer server, {List args}) { |
424 var tokenEndpoint = server.url.then((url) => | 426 var tokenEndpoint = server.url.then((url) => |
425 url.resolve('/token').toString()); | 427 url.resolve('/token').toString()); |
426 if (args == null) args = []; | 428 if (args == null) args = []; |
427 args = flatten(['lish', '--server', tokenEndpoint, args]); | 429 args = flatten(['lish', '--server', tokenEndpoint, args]); |
428 return startPub(args: args, tokenEndpoint: tokenEndpoint); | 430 return startPub(args: args, tokenEndpoint: tokenEndpoint); |
429 } | 431 } |
430 | 432 |
431 /// Handles the beginning confirmation process for uploading a packages. | 433 /// Handles the beginning confirmation process for uploading a packages. |
432 /// Ensures that the right output is shown and then enters "y" to confirm the | 434 /// Ensures that the right output is shown and then enters "y" to confirm the |
433 /// upload. | 435 /// upload. |
434 void confirmPublish(ScheduledProcess pub) { | 436 void confirmPublish(ScheduledProcess pub) { |
435 // TODO(rnystrom): This is overly specific and inflexible regarding different | 437 // TODO(rnystrom): This is overly specific and inflexible regarding different |
436 // test packages. Should validate this a little more loosely. | 438 // test packages. Should validate this a little more loosely. |
437 expect(pub.nextLine(), completion(startsWith( | 439 pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to ')); |
438 'Publishing test_pkg 1.0.0 to '))); | 440 pub.stdout.expect(emitsLines( |
439 expect(pub.nextLine(), completion(equals("|-- LICENSE"))); | 441 "|-- LICENSE\n" |
440 expect(pub.nextLine(), completion(equals("|-- lib"))); | 442 "|-- lib\n" |
441 expect(pub.nextLine(), completion(equals("| '-- test_pkg.dart"))); | 443 "| '-- test_pkg.dart\n" |
442 expect(pub.nextLine(), completion(equals("'-- pubspec.yaml"))); | 444 "'-- pubspec.yaml\n" |
443 expect(pub.nextLine(), completion(equals(""))); | 445 "\n" |
444 expect(pub.nextLine(), completion(equals('Looks great! Are you ready to ' | 446 "Looks great! Are you ready to upload your package (y/n)?")); |
445 'upload your package (y/n)?'))); | |
446 | |
447 pub.writeLine("y"); | 447 pub.writeLine("y"); |
448 } | 448 } |
449 | 449 |
450 /// Starts a Pub process and returns a [ScheduledProcess] that supports | 450 /// Starts a Pub process and returns a [ScheduledProcess] that supports |
451 /// interaction with that process. | 451 /// interaction with that process. |
452 /// | 452 /// |
453 /// Any futures in [args] will be resolved before the process is started. | 453 /// Any futures in [args] will be resolved before the process is started. |
454 ScheduledProcess startPub({List args, Future<Uri> tokenEndpoint}) { | 454 ScheduledProcess startPub({List args, Future<Uri> tokenEndpoint}) { |
455 String pathInSandbox(String relPath) { | 455 String pathInSandbox(String relPath) { |
456 return path.join(path.absolute(sandboxDir), relPath); | 456 return path.join(path.absolute(sandboxDir), relPath); |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
500 | 500 |
501 return environment; | 501 return environment; |
502 }); | 502 }); |
503 | 503 |
504 return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture, | 504 return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture, |
505 workingDirectory: pathInSandbox(appPath), | 505 workingDirectory: pathInSandbox(appPath), |
506 description: args.isEmpty ? 'pub' : 'pub ${args.first}'); | 506 description: args.isEmpty ? 'pub' : 'pub ${args.first}'); |
507 } | 507 } |
508 | 508 |
509 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output | 509 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output |
510 /// and makes [nextLine], [nextErrLine], [remainingStdout], and | 510 /// and makes [stdout] and [stderr] work as though pub weren't running in |
511 /// [remainingStderr] work as though pub weren't running in verbose mode. | 511 /// verbose mode. |
512 class PubProcess extends ScheduledProcess { | 512 class PubProcess extends ScheduledProcess { |
513 Stream<Pair<log.Level, String>> _log; | 513 Stream<Pair<log.Level, String>> _log; |
514 Stream<String> _stdout; | 514 Stream<String> _stdout; |
515 Stream<String> _stderr; | 515 Stream<String> _stderr; |
516 | 516 |
517 PubProcess.start(executable, arguments, | 517 PubProcess.start(executable, arguments, |
518 {workingDirectory, environment, String description, | 518 {workingDirectory, environment, String description, |
519 Encoding encoding: UTF8}) | 519 Encoding encoding: UTF8}) |
520 : super.start(executable, arguments, | 520 : super.start(executable, arguments, |
521 workingDirectory: workingDirectory, | 521 workingDirectory: workingDirectory, |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
567 } | 567 } |
568 | 568 |
569 var pair = tee(_stdout); | 569 var pair = tee(_stdout); |
570 _stdout = pair.first; | 570 _stdout = pair.first; |
571 return pair.last; | 571 return pair.last; |
572 } | 572 } |
573 | 573 |
574 Stream<String> stderrStream() { | 574 Stream<String> stderrStream() { |
575 if (_stderr == null) { | 575 if (_stderr == null) { |
576 _stderr = _logStream().expand((entry) { | 576 _stderr = _logStream().expand((entry) { |
577 if (entry.first != log.Level.ERROR && entry.first != log.Level.WARNING)
{ | 577 if (entry.first != log.Level.ERROR && |
| 578 entry.first != log.Level.WARNING) { |
578 return []; | 579 return []; |
579 } | 580 } |
580 return [entry.last]; | 581 return [entry.last]; |
581 }); | 582 }); |
582 } | 583 } |
583 | 584 |
584 var pair = tee(_stderr); | 585 var pair = tee(_stderr); |
585 _stderr = pair.first; | 586 _stderr = pair.first; |
586 return pair.last; | 587 return pair.last; |
587 } | 588 } |
(...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
859 bool matches(item, Map matchState) { | 860 bool matches(item, Map matchState) { |
860 if (item is! Pair) return false; | 861 if (item is! Pair) return false; |
861 return _firstMatcher.matches(item.first, matchState) && | 862 return _firstMatcher.matches(item.first, matchState) && |
862 _lastMatcher.matches(item.last, matchState); | 863 _lastMatcher.matches(item.last, matchState); |
863 } | 864 } |
864 | 865 |
865 Description describe(Description description) { | 866 Description describe(Description description) { |
866 description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); | 867 description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); |
867 } | 868 } |
868 } | 869 } |
| 870 |
| 871 /// A [StreamMatcher] that matches multiple lines of output. |
| 872 StreamMatcher emitsLines(String output) => inOrder(output.split("\n")); |
OLD | NEW |