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

Side by Side Diff: mojo/public/dart/third_party/test/lib/src/utils.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 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 unified diff | Download patch
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library test.utils;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:math' as math;
10
11 import 'package:crypto/crypto.dart';
12 import 'package:path/path.dart' as p;
13 import 'package:shelf/shelf.dart' as shelf;
14 import 'package:stack_trace/stack_trace.dart';
15
16 import 'backend/operating_system.dart';
17 import 'util/cancelable_future.dart';
18 import 'util/path_handler.dart';
19 import 'util/stream_queue.dart';
20
21 /// The maximum console line length.
22 const _lineLength = 100;
23
24 /// A typedef for a possibly-asynchronous function.
25 ///
26 /// The return type should only ever by [Future] or void.
27 typedef AsyncFunction();
28
29 /// A typedef for a zero-argument callback function.
30 typedef void Callback();
31
32 /// A converter that decodes bytes using UTF-8 and splits them on newlines.
33 final lineSplitter = UTF8.decoder.fuse(const LineSplitter());
34
35 /// A regular expression to match the exception prefix that some exceptions'
36 /// [Object.toString] values contain.
37 final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): ');
38
39 /// Directories that are specific to OS X.
40 ///
41 /// This is used to try to distinguish OS X and Linux in [currentOSGuess].
42 final _macOSDirectories = new Set<String>.from([
43 "/Applications",
44 "/Library",
45 "/Network",
46 "/System",
47 "/Users"
48 ]);
49
50 /// Returns the best guess for the current operating system without using
51 /// `dart:io`.
52 ///
53 /// This is useful for running test files directly and skipping tests as
54 /// appropriate. The only OS-specific information we have is the current path,
55 /// which we try to use to figure out the OS.
56 final OperatingSystem currentOSGuess = (() {
57 if (p.style == p.Style.url) return OperatingSystem.none;
58 if (p.style == p.Style.windows) return OperatingSystem.windows;
59 if (_macOSDirectories.any(p.current.startsWith)) return OperatingSystem.macOS;
60 return OperatingSystem.linux;
61 })();
62
63 /// A pair of values.
64 class Pair<E, F> {
65 E first;
66 F last;
67
68 Pair(this.first, this.last);
69
70 String toString() => '($first, $last)';
71
72 bool operator==(other) {
73 if (other is! Pair) return false;
74 return other.first == first && other.last == last;
75 }
76
77 int get hashCode => first.hashCode ^ last.hashCode;
78 }
79
80 /// Get a string description of an exception.
81 ///
82 /// Many exceptions include the exception class name at the beginning of their
83 /// [toString], so we remove that if it exists.
84 String getErrorMessage(error) =>
85 error.toString().replaceFirst(_exceptionPrefix, '');
86
87 /// Indent each line in [str] by two spaces.
88 String indent(String str) =>
89 str.replaceAll(new RegExp("^", multiLine: true), " ");
90
91 /// Returns a sentence fragment listing the elements of [iter].
92 ///
93 /// This converts each element of [iter] to a string and separates them with
94 /// commas and/or "and" where appropriate.
95 String toSentence(Iterable iter) {
96 if (iter.length == 1) return iter.first.toString();
97 var result = iter.take(iter.length - 1).join(", ");
98 if (iter.length > 2) result += ",";
99 return "$result and ${iter.last}";
100 }
101
102 /// Wraps [text] so that it fits within [lineLength], which defaults to 100
103 /// characters.
104 ///
105 /// This preserves existing newlines and doesn't consider terminal color escapes
106 /// part of a word's length.
107 String wordWrap(String text, {int lineLength}) {
108 if (lineLength == null) lineLength = _lineLength;
109 return text.split("\n").map((originalLine) {
110 var buffer = new StringBuffer();
111 var lengthSoFar = 0;
112 for (var word in originalLine.split(" ")) {
113 var wordLength = withoutColors(word).length;
114 if (wordLength > lineLength) {
115 if (lengthSoFar != 0) buffer.writeln();
116 buffer.writeln(word);
117 } else if (lengthSoFar == 0) {
118 buffer.write(word);
119 lengthSoFar = wordLength;
120 } else if (lengthSoFar + 1 + wordLength > lineLength) {
121 buffer.writeln();
122 buffer.write(word);
123 lengthSoFar = wordLength;
124 } else {
125 buffer.write(" $word");
126 lengthSoFar += 1 + wordLength;
127 }
128 }
129 return buffer.toString();
130 }).join("\n");
131 }
132
133 /// A regular expression matching terminal color codes.
134 final _colorCode = new RegExp('\u001b\\[[0-9;]+m');
135
136 /// Returns [str] without any color codes.
137 String withoutColors(String str) => str.replaceAll(_colorCode, '');
138
139 /// A regular expression matching the path to a temporary file used to start an
140 /// isolate.
141 ///
142 /// These paths aren't relevant and are removed from stack traces.
143 final _isolatePath =
144 new RegExp(r"/test_[A-Za-z0-9]{6}/runInIsolate\.dart$");
145
146 /// Returns [stackTrace] converted to a [Chain] with all irrelevant frames
147 /// folded together.
148 ///
149 /// If [verbose] is `true`, returns the chain for [stackTrace] unmodified.
150 Chain terseChain(StackTrace stackTrace, {bool verbose: false}) {
151 if (verbose) return new Chain.forTrace(stackTrace);
152 return new Chain.forTrace(stackTrace).foldFrames((frame) {
153 if (frame.package == 'test') return true;
154
155 // Filter out frames from our isolate bootstrap as well.
156 if (frame.uri.scheme != 'file') return false;
157 return frame.uri.path.contains(_isolatePath);
158 }, terse: true);
159 }
160
161 /// Flattens nested [Iterable]s inside an [Iterable] into a single [List]
162 /// containing only non-[Iterable] elements.
163 List flatten(Iterable nested) {
164 var result = [];
165 helper(iter) {
166 for (var element in iter) {
167 if (element is Iterable) {
168 helper(element);
169 } else {
170 result.add(element);
171 }
172 }
173 }
174 helper(nested);
175 return result;
176 }
177
178 /// Returns a new map with all values in both [map1] and [map2].
179 ///
180 /// If there are conflicting keys, [map2]'s value wins.
181 Map mergeMaps(Map map1, Map map2) {
182 var result = {};
183 map1.forEach((key, value) {
184 result[key] = value;
185 });
186 map2.forEach((key, value) {
187 result[key] = value;
188 });
189 return result;
190 }
191
192 /// Returns a sink that maps events sent to [original] using [fn].
193 StreamSink mapSink(StreamSink original, fn(event)) {
194 var controller = new StreamController(sync: true);
195 controller.stream.listen(
196 (event) => original.add(fn(event)),
197 onError: (error, stackTrace) => original.addError(error, stackTrace),
198 onDone: () => original.close());
199 return controller.sink;
200 }
201
202 /// Like [runZoned], but [zoneValues] are set for the callbacks in
203 /// [zoneSpecification] and [onError].
204 runZonedWithValues(body(), {Map zoneValues,
205 ZoneSpecification zoneSpecification, Function onError}) {
206 return runZoned(() {
207 return runZoned(body,
208 zoneSpecification: zoneSpecification, onError: onError);
209 }, zoneValues: zoneValues);
210 }
211
212 /// Truncates [text] to fit within [maxLength].
213 ///
214 /// This will try to truncate along word boundaries and preserve words both at
215 /// the beginning and the end of [text].
216 String truncate(String text, int maxLength) {
217 // Return the full message if it fits.
218 if (text.length <= maxLength) return text;
219
220 // If we can fit the first and last three words, do so.
221 var words = text.split(' ');
222 if (words.length > 1) {
223 var i = words.length;
224 var length = words.first.length + 4;
225 do {
226 i--;
227 length += 1 + words[i].length;
228 } while (length <= maxLength && i > 0);
229 if (length > maxLength || i == 0) i++;
230 if (i < words.length - 4) {
231 // Require at least 3 words at the end.
232 var buffer = new StringBuffer();
233 buffer.write(words.first);
234 buffer.write(' ...');
235 for ( ; i < words.length; i++) {
236 buffer.write(' ');
237 buffer.write(words[i]);
238 }
239 return buffer.toString();
240 }
241 }
242
243 // Otherwise truncate to return the trailing text, but attempt to start at
244 // the beginning of a word.
245 var result = text.substring(text.length - maxLength + 4);
246 var firstSpace = result.indexOf(' ');
247 if (firstSpace > 0) {
248 result = result.substring(firstSpace);
249 }
250 return '...$result';
251 }
252
253 /// Returns a human-friendly representation of [duration].
254 String niceDuration(Duration duration) {
255 var minutes = duration.inMinutes;
256 var seconds = duration.inSeconds % 59;
257 var decaseconds = (duration.inMilliseconds % 1000) ~/ 100;
258
259 var buffer = new StringBuffer();
260 if (minutes != 0) buffer.write("$minutes minutes");
261
262 if (minutes == 0 || seconds != 0) {
263 if (minutes != 0) buffer.write(", ");
264 buffer.write(seconds);
265 if (decaseconds != 0) buffer.write(".$decaseconds");
266 buffer.write(" seconds");
267 }
268
269 return buffer.toString();
270 }
271
272 /// Merges [streams] into a single stream that emits events from all sources.
273 Stream mergeStreams(Iterable<Stream> streamIter) {
274 var streams = streamIter.toList();
275
276 var subscriptions = new Set();
277 var controller;
278 controller = new StreamController(sync: true, onListen: () {
279 for (var stream in streams) {
280 var subscription;
281 subscription = stream.listen(
282 controller.add,
283 onError: controller.addError,
284 onDone: () {
285 subscriptions.remove(subscription);
286 if (subscriptions.isEmpty) controller.close();
287 });
288 subscriptions.add(subscription);
289 }
290 }, onPause: () {
291 for (var subscription in subscriptions) {
292 subscription.pause();
293 }
294 }, onResume: () {
295 for (var subscription in subscriptions) {
296 subscription.resume();
297 }
298 }, onCancel: () {
299 for (var subscription in subscriptions) {
300 subscription.cancel();
301 }
302 });
303
304 return controller.stream;
305 }
306
307 /// Returns the first value [stream] emits, or `null` if [stream] closes before
308 /// emitting a value.
309 Future maybeFirst(Stream stream) {
310 var completer = new Completer();
311
312 var subscription;
313 subscription = stream.listen((data) {
314 completer.complete(data);
315 subscription.cancel();
316 }, onError: (error, stackTrace) {
317 completer.completeError(error, stackTrace);
318 subscription.cancel();
319 }, onDone: () {
320 completer.complete();
321 });
322
323 return completer.future;
324 }
325
326 /// Returns a [CancelableFuture] that returns the next value of [queue] unless
327 /// it's canceled.
328 ///
329 /// If the future is canceled, [queue] is not moved forward at all. Note that
330 /// it's not safe to call further methods on [queue] until this future has
331 /// either completed or been canceled.
332 CancelableFuture cancelableNext(StreamQueue queue) {
333 var fork = queue.fork();
334 var completer = new CancelableCompleter(() => fork.cancel(immediate: true));
335 completer.complete(fork.next.then((_) {
336 fork.cancel();
337 return queue.next;
338 }));
339 return completer.future;
340 }
341
342 /// Returns a single-subscription stream that emits the results of [futures] in
343 /// the order they complete.
344 ///
345 /// If any futures in [futures] are [CancelableFuture]s, this will cancel them
346 /// if the subscription is canceled.
347 Stream inCompletionOrder(Iterable<Future> futures) {
348 var futureSet = futures.toSet();
349 var controller = new StreamController(sync: true, onCancel: () {
350 return Future.wait(futureSet.map((future) {
351 return future is CancelableFuture ? future.cancel() : null;
352 }).where((future) => future != null));
353 });
354
355 for (var future in futureSet) {
356 future.then(controller.add).catchError(controller.addError)
357 .whenComplete(() {
358 futureSet.remove(future);
359 if (futureSet.isEmpty) controller.close();
360 });
361 }
362
363 return controller.stream;
364 }
365
366 /// Returns a stream that emits [error] and [stackTrace], then closes.
367 ///
368 /// This is useful for adding errors to streams defined via `async*`.
369 Stream errorStream(error, StackTrace stackTrace) {
370 var controller = new StreamController();
371 controller.addError(error, stackTrace);
372 controller.close();
373 return controller.stream;
374 }
375
376 /// Runs [fn] and discards its return value.
377 ///
378 /// This is useful for making a block of code async without forcing the
379 /// containing method to return a future.
380 void invoke(fn()) {
381 fn();
382 }
383
384 /// Returns a random base64 string containing [bytes] bytes of data.
385 ///
386 /// [seed] is passed to [math.Random]; [urlSafe] and [addLineSeparator] are
387 /// passed to [CryptoUtils.bytesToBase64].
388 String randomBase64(int bytes, {int seed, bool urlSafe: false,
389 bool addLineSeparator: false}) {
390 var random = new math.Random(seed);
391 var data = [];
392 for (var i = 0; i < bytes; i++) {
393 data.add(random.nextInt(256));
394 }
395 return CryptoUtils.bytesToBase64(data,
396 urlSafe: urlSafe, addLineSeparator: addLineSeparator);
397 }
398
399 /// Returns middleware that nests all requests beneath the URL prefix [beneath].
400 shelf.Middleware nestingMiddleware(String beneath) {
401 return (handler) {
402 var pathHandler = new PathHandler()..add(beneath, handler);
403 return pathHandler.handler;
404 };
405 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698