| // 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/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)); | 
 |       final newline = utf8.encode("\n"); | 
 |       if (_data.last != newline.last) { | 
 |         _data.addAll(newline); | 
 |       } | 
 |       _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)); | 
 |       final newline = utf8.encode("\n"); | 
 |       if (_data.last != newline.last) { | 
 |         _data.addAll(newline); | 
 |       } | 
 |       _data.addAll(utf8.encode(_nonUtf8Error)); | 
 |       return true; | 
 |     } | 
 |   } | 
 |  | 
 |   @override | 
 |   Future<void> addStream(Stream<List<int>> stream) { | 
 |     _subscription = stream.listen(add); | 
 |     return _subscription?.asFuture() ?? Future.value(); | 
 |   } | 
 |  | 
 |   @override | 
 |   Future<void> close() => _subscription?.cancel() ?? Future.value(); | 
 |  | 
 |   Future<void> cancel() => _subscription?.cancel() ?? Future.value(); | 
 | } | 
 |  | 
 | /// 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<void> close() { | 
 |     return Future.wait([ | 
 |       super.close(), | 
 |       if (_sink != null) _sink!.flush().whenComplete(_sink!.close) | 
 |     ]); | 
 |   } | 
 |  | 
 |   @override | 
 |   Future<void> cancel() { | 
 |     return Future.wait([ | 
 |       super.cancel(), | 
 |       if (_sink != null) _sink!.flush().whenComplete(_sink!.close) | 
 |     ]); | 
 |   } | 
 | } |