OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 library test.test.io; | 5 library test.test.io; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 | 9 |
10 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
| 11 import 'package:scheduled_test/descriptor.dart' as d; |
| 12 import 'package:scheduled_test/scheduled_process.dart'; |
| 13 import 'package:scheduled_test/scheduled_stream.dart'; |
| 14 import 'package:scheduled_test/scheduled_test.dart'; |
11 import 'package:test/src/util/io.dart'; | 15 import 'package:test/src/util/io.dart'; |
12 import 'package:test/src/utils.dart'; | |
13 | 16 |
14 /// The path to the root directory of the `test` package. | 17 /// The path to the root directory of the `test` package. |
15 final String packageDir = p.dirname(p.dirname(libraryPath(#test.test.io))); | 18 final String packageDir = p.dirname(p.dirname(libraryPath(#test.test.io))); |
16 | 19 |
17 /// The path to the `pub` executable in the current Dart SDK. | 20 /// The path to the `pub` executable in the current Dart SDK. |
18 final _pubPath = p.absolute(p.join( | 21 final _pubPath = p.absolute(p.join( |
19 p.dirname(Platform.executable), | 22 p.dirname(Platform.executable), |
20 Platform.isWindows ? 'pub.bat' : 'pub')); | 23 Platform.isWindows ? 'pub.bat' : 'pub')); |
21 | 24 |
22 /// The platform-specific message emitted when a nonexistent file is loaded. | 25 /// The platform-specific message emitted when a nonexistent file is loaded. |
23 final String noSuchFileMessage = Platform.isWindows | 26 final String noSuchFileMessage = Platform.isWindows |
24 ? "The system cannot find the file specified." | 27 ? "The system cannot find the file specified." |
25 : "No such file or directory"; | 28 : "No such file or directory"; |
26 | 29 |
27 /// A regular expression that matches the output of "pub serve". | 30 /// A regular expression that matches the output of "pub serve". |
28 final _servingRegExp = | 31 final _servingRegExp = |
29 new RegExp(r'^Serving myapp [a-z]+ on http://localhost:(\d+)$'); | 32 new RegExp(r'^Serving myapp [a-z]+ on http://localhost:(\d+)$'); |
30 | 33 |
| 34 /// A future that will return the port of a pub serve instance run via |
| 35 /// [runPubServe]. |
| 36 /// |
| 37 /// This should only be called after [runPubServe]. |
| 38 Future<int> get pubServePort => _pubServePortCompleter.future; |
| 39 Completer<int> _pubServePortCompleter; |
| 40 |
| 41 /// The path to the sandbox directory. |
| 42 /// |
| 43 /// This is only set in tests for which [useSandbox] is active. |
| 44 String get sandbox => _sandbox; |
| 45 String _sandbox; |
| 46 |
| 47 /// Declares a [setUp] function that creates a sandbox diretory and sets it as |
| 48 /// the default for scheduled_test's directory descriptors. |
| 49 /// |
| 50 /// This should be called outside of any tests. If [additionalSetup] is passed, |
| 51 /// it's run after the sandbox creation has been scheduled. |
| 52 void useSandbox([void additionalSetup()]) { |
| 53 setUp(() { |
| 54 _sandbox = createTempDir(); |
| 55 d.defaultRoot = _sandbox; |
| 56 |
| 57 currentSchedule.onComplete.schedule(() { |
| 58 try { |
| 59 new Directory(_sandbox).deleteSync(recursive: true); |
| 60 } on IOException catch (_) { |
| 61 // Silently swallow exceptions on Windows. If the test failed, there may |
| 62 // still be lingering processes that have files in the sandbox open, |
| 63 // which will cause this to fail on Windows. |
| 64 if (!Platform.isWindows) rethrow; |
| 65 } |
| 66 }, 'deleting the sandbox directory'); |
| 67 |
| 68 if (additionalSetup != null) additionalSetup(); |
| 69 }); |
| 70 } |
| 71 |
| 72 /// Expects that the entire stdout stream of [test] equals [expected]. |
| 73 void expectStdoutEquals(ScheduledProcess test, String expected) => |
| 74 _expectStreamEquals(test.stdoutStream(), expected); |
| 75 |
| 76 /// Expects that the entire stderr stream of [test] equals [expected]. |
| 77 void expectStderrEquals(ScheduledProcess test, String expected) => |
| 78 _expectStreamEquals(test.stderrStream(), expected); |
| 79 |
| 80 /// Expects that the entirety of the line stream [stream] equals [expected]. |
| 81 void _expectStreamEquals(Stream<String> stream, String expected) { |
| 82 expect((() async { |
| 83 var lines = await stream.toList(); |
| 84 expect(lines.join("\n").trim(), equals(expected.trim())); |
| 85 })(), completes); |
| 86 } |
| 87 |
| 88 /// Returns a [StreamMatcher] that asserts that the stream emits strings |
| 89 /// containing each string in [strings] in order. |
| 90 /// |
| 91 /// This expects each string in [strings] to match a different string in the |
| 92 /// stream. |
| 93 StreamMatcher containsInOrder(Iterable<String> strings) => |
| 94 inOrder(strings.map((string) => consumeThrough(contains(string)))); |
| 95 |
31 /// Runs the test executable with the package root set properly. | 96 /// Runs the test executable with the package root set properly. |
32 ProcessResult runTest(List<String> args, {String workingDirectory, | 97 ScheduledProcess runTest(List args, {bool compact: false, |
33 Map<String, String> environment}) { | 98 int concurrency, Map<String, String> environment}) { |
| 99 if (concurrency == null) concurrency = 1; |
| 100 |
34 var allArgs = [ | 101 var allArgs = [ |
35 p.absolute(p.join(packageDir, 'bin/test.dart')), | 102 p.absolute(p.join(packageDir, 'bin/test.dart')), |
36 "--package-root=${p.join(packageDir, 'packages')}" | 103 "--package-root=${p.join(packageDir, 'packages')}", |
37 ]..addAll(args); | 104 "--concurrency=$concurrency" |
| 105 ]; |
| 106 |
| 107 if (!compact) allArgs.addAll(["-r", "expanded"]); |
| 108 allArgs.addAll(args); |
38 | 109 |
39 if (environment == null) environment = {}; | 110 if (environment == null) environment = {}; |
40 environment.putIfAbsent("_UNITTEST_USE_COLOR", () => "false"); | 111 environment.putIfAbsent("_UNITTEST_USE_COLOR", () => "false"); |
41 | 112 |
42 // TODO(nweiz): Use ScheduledProcess once it's compatible. | 113 return runDart(allArgs, |
43 return runDart(allArgs, workingDirectory: workingDirectory, | 114 environment: environment, |
44 environment: environment); | 115 description: "dart bin/test.dart"); |
45 } | 116 } |
46 | 117 |
47 /// Runs Dart. | 118 /// Runs Dart. |
48 ProcessResult runDart(List<String> args, {String workingDirectory, | 119 ScheduledProcess runDart(List args, {Map<String, String> environment, |
49 Map<String, String> environment}) { | 120 String description}) { |
50 var allArgs = Platform.executableArguments.map((arg) { | 121 var allArgs = Platform.executableArguments.map((arg) { |
51 // The package root might be relative, so we need to make it absolute if | 122 // The package root might be relative, so we need to make it absolute if |
52 // we're going to run in a different working directory. | 123 // we're going to run in a different working directory. |
53 if (!arg.startsWith("--package-root=")) return arg; | 124 if (!arg.startsWith("--package-root=")) return arg; |
54 return "--package-root=" + | 125 return "--package-root=" + |
55 p.absolute(p.fromUri(arg.substring("--package-root=".length))); | 126 p.absolute(p.fromUri(arg.substring("--package-root=".length))); |
56 }).toList()..addAll(args); | 127 }).toList()..addAll(args); |
57 | 128 |
58 // TODO(nweiz): Use ScheduledProcess once it's compatible. | 129 return new ScheduledProcess.start( |
59 return new _NormalizedProcessResult(Process.runSync( | |
60 p.absolute(Platform.executable), allArgs, | 130 p.absolute(Platform.executable), allArgs, |
61 workingDirectory: workingDirectory, environment: environment)); | 131 workingDirectory: _sandbox, |
| 132 environment: environment, |
| 133 description: description); |
62 } | 134 } |
63 | 135 |
64 /// Runs Pub. | 136 /// Runs Pub. |
65 ProcessResult runPub(List<String> args, {String workingDirectory, | 137 ScheduledProcess runPub(List args, {Map<String, String> environment}) { |
66 Map<String, String> environment}) { | 138 return new ScheduledProcess.start( |
67 // TODO(nweiz): Use ScheduledProcess once it's compatible. | |
68 return new _NormalizedProcessResult(Process.runSync( | |
69 _pubPath, args, | 139 _pubPath, args, |
70 workingDirectory: workingDirectory, environment: environment)); | 140 workingDirectory: _sandbox, |
| 141 environment: environment, |
| 142 description: "pub ${args.first}"); |
71 } | 143 } |
72 | 144 |
73 /// Starts the test executable with the package root set properly. | 145 /// Runs "pub serve". |
74 Future<Process> startTest(List<String> args, {String workingDirectory, | 146 /// |
| 147 /// This returns assigns [_pubServePort] to a future that will complete to the |
| 148 /// port of the "pub serve" instance. |
| 149 ScheduledProcess runPubServe({List args, String workingDirectory, |
75 Map<String, String> environment}) { | 150 Map<String, String> environment}) { |
76 var allArgs = [ | 151 _pubServePortCompleter = new Completer(); |
77 p.absolute(p.join(packageDir, 'bin/test.dart')), | 152 currentSchedule.onComplete.schedule(() => _pubServePortCompleter = null); |
78 "--package-root=${p.join(packageDir, 'packages')}" | |
79 ]..addAll(args); | |
80 | 153 |
81 if (environment == null) environment = {}; | |
82 environment.putIfAbsent("_UNITTEST_USE_COLOR", () => "false"); | |
83 | |
84 return startDart(allArgs, workingDirectory: workingDirectory, | |
85 environment: environment); | |
86 } | |
87 | |
88 /// Starts Dart. | |
89 Future<Process> startDart(List<String> args, {String workingDirectory, | |
90 Map<String, String> environment}) { | |
91 var allArgs = Platform.executableArguments.toList()..addAll(args); | |
92 | |
93 // TODO(nweiz): Use ScheduledProcess once it's compatible. | |
94 return Process.start(Platform.executable, allArgs, | |
95 workingDirectory: workingDirectory, environment: environment); | |
96 } | |
97 | |
98 /// Starts Pub. | |
99 Future<Process> startPub(List<String> args, {String workingDirectory, | |
100 Map<String, String> environment}) { | |
101 // TODO(nweiz): Use ScheduledProcess once it's compatible. | |
102 return Process.start(_pubPath, args, | |
103 workingDirectory: workingDirectory, environment: environment); | |
104 } | |
105 | |
106 /// Starts "pub serve". | |
107 /// | |
108 /// This returns a pair of the pub serve process and the port it's serving on. | |
109 Future<Pair<Process, int>> startPubServe({List<String> args, | |
110 String workingDirectory, Map<String, String> environment}) async { | |
111 var allArgs = ['serve', '--port', '0']; | 154 var allArgs = ['serve', '--port', '0']; |
112 if (args != null) allArgs.addAll(args); | 155 if (args != null) allArgs.addAll(args); |
113 | 156 |
114 var process = await startPub(allArgs, | 157 var pub = runPub(allArgs, environment: environment); |
115 workingDirectory: workingDirectory, environment: environment); | |
116 var line = await lineSplitter.bind(process.stdout) | |
117 .firstWhere(_servingRegExp.hasMatch); | |
118 var match = _servingRegExp.firstMatch(line); | |
119 | 158 |
120 return new Pair(process, int.parse(match[1])); | 159 schedule(() async { |
| 160 var match; |
| 161 while (match == null) { |
| 162 var line = await pub.stdout.next(); |
| 163 match = _servingRegExp.firstMatch(line); |
| 164 } |
| 165 _pubServePortCompleter.complete(int.parse(match[1])); |
| 166 }, "waiting for pub serve to emit its port number"); |
| 167 |
| 168 return pub; |
121 } | 169 } |
122 | |
123 /// A wrapper around [ProcessResult] that normalizes the newline format across | |
124 /// operating systems. | |
125 class _NormalizedProcessResult implements ProcessResult { | |
126 final ProcessResult _inner; | |
127 | |
128 int get exitCode => _inner.exitCode; | |
129 int get pid => _inner.pid; | |
130 | |
131 final String stdout; | |
132 final String stderr; | |
133 | |
134 _NormalizedProcessResult(ProcessResult inner) | |
135 : _inner = inner, | |
136 stdout = Platform.isWindows | |
137 ? inner.stdout.replaceAll("\r\n", "\n") | |
138 : inner.stdout, | |
139 stderr = Platform.isWindows | |
140 ? inner.stderr.replaceAll("\r\n", "\n") | |
141 : inner.stderr; | |
142 } | |
OLD | NEW |