blob: 9a14b700078c53c27c68713f9c6541adfe7e9e0f [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:path/path.dart' as p;
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:test/test.dart';
import 'sandbox.dart';
/// A UTF-8 codec that allows malformed byte sequences.
final utf8 = const Utf8Codec(allowMalformed: true);
/// Prepends a vertical bar to [text].
String addBar(String text) => prefixLines(text, "${glyph.verticalLine} ",
first: "${glyph.downEnd} ", last: "${glyph.upEnd} ", single: "| ");
/// Indents [text], and adds a bullet at the beginning.
String addBullet(String text) =>
prefixLines(text, " ", first: "${glyph.bullet} ");
/// Converts [strings] to a bulleted list.
String bullet(Iterable<String> strings) =>"\n");
/// Prepends each line in [text] with [prefix].
/// If [first] or [last] is passed, the first and last lines, respectively, are
/// prefixed with those instead. If [single] is passed, it's used if there's
/// only a single line; otherwise, [first], [last], or [prefix] is used, in that
/// order of precedence.
String prefixLines(String text, String prefix,
{String first, String last, String single}) {
first ??= prefix;
last ??= prefix;
single ??= first ?? last ?? prefix;
var lines = text.split('\n');
if (lines.length == 1) return "$single$text";
var buffer = StringBuffer("$first${lines.first}\n");
for (var line in lines.skip(1).take(lines.length - 2)) {
return buffer.toString();
/// Returns a representation of [path] that's easy for humans to read.
/// This may not be a valid path relative to [p.current].
String prettyPath(String path) {
if (sandboxExists && p.isWithin(sandbox, path)) {
return p.relative(path, from: sandbox);
} else if (p.isWithin(p.current, path)) {
return p.relative(path);
} else {
return path;
/// Returns whether [pattern] matches all of [string].
bool matchesAll(Pattern pattern, String string) =>
pattern.matchAsPrefix(string)?.end == string.length;
/// Like [Future.wait] with `eagerError: true`, but reports errors after the
/// first using [registerException] rather than silently ignoring them.
Future waitAndReportErrors(Iterable<Future> futures) {
var errored = false;
return Future.wait( {
// Avoid async/await so that we synchronously add error handlers for the
// futures to keep them from top-leveling.
return future.catchError((error, StackTrace stackTrace) {
if (!errored) {
errored = true;
throw error;
} else {
registerException(error, stackTrace);