blob: fa8caae58b126f8ce091699d96d0353e3899a91a [file] [log] [blame]
// Copyright (c) 2019, 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 'dart:io';
const _nonUtf8Error = '[test.dart: This test output contains non-UTF8 data]';
const _truncatedError =
'[test.dart: This test output was too long and was truncated here.';
/// Records the output from a test.
class OutputLog implements StreamConsumer<List<int>> {
// TODO(45618): Reduce this if language_2/unsorted/disassemble_test is fixed
// to produce less output and any other large-output tests are fixed.
static const _maxLength = 10 * 1024 * 1024;
final List<int> _data = [];
StreamSubscription _subscription;
bool get hasNonUtf8 => _hasNonUtf8 ??= _checkUtf8();
bool _hasNonUtf8;
bool get wasTruncated => _wasTruncated;
bool _wasTruncated = false;
List<int> get bytes => _data;
void add(List<int> data) {
if (_hasNonUtf8 != null) {
throw StateError("Cannot add to OutputLog after accessing bytes.");
}
// Discard additional output after we've reached the limit.
if (_wasTruncated) return;
if (_data.length + data.length > _maxLength) {
_data.addAll(data.take(_maxLength - _data.length));
_data.addAll(utf8.encode(_truncatedError));
_wasTruncated = true;
} else {
_data.addAll(data);
}
}
void clear() {
_data.clear();
}
bool _checkUtf8() {
try {
utf8.decode(_data, allowMalformed: false);
return false;
} on FormatException {
var malformed = utf8.decode(_data, allowMalformed: true);
_data.clear();
_data.addAll(utf8.encode(malformed));
_data.addAll(utf8.encode(_nonUtf8Error));
return true;
}
}
@override
Future addStream(Stream<List<int>> stream) {
_subscription = stream.listen(add);
return _subscription.asFuture();
}
@override
Future close() => _subscription?.cancel();
Future cancel() => _subscription?.cancel();
}
/// An [OutputLog] that tees the output to a file as well.
class FileOutputLog extends OutputLog {
final File _outputFile;
IOSink _sink;
FileOutputLog(this._outputFile);
@override
void add(List<int> data) {
super.add(data);
_sink ??= _outputFile.openWrite();
_sink.add(data);
}
@override
Future close() {
return Future.wait([
super.close(),
if (_sink != null) _sink.flush().whenComplete(_sink.close)
]);
}
@override
Future cancel() {
return Future.wait([
super.cancel(),
if (_sink != null) _sink.flush().whenComplete(_sink.close)
]);
}
}