// Copyright (c) 2021, 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. @Deprecated('Tests deprecated functionality') library; import 'dart:async'; import 'dart:convert'; import 'package:async/async.dart'; import 'package:test/test.dart'; const int letterA = 0x41; void main() { // We don't explicitly test [EventSinkBase] because it shares all the relevant // implementation with [StreamSinkBase]. group('StreamSinkBase', () { test('forwards add() to onAdd()', () { var sink = _StreamSink( onAdd: expectAsync1((value) { expect(value, equals(123)); }), ); sink.add(123); }); test('forwards addError() to onError()', () { var sink = _StreamSink( onError: expectAsync2((error, [stackTrace]) { expect(error, equals('oh no')); expect(stackTrace, isA()); }), ); sink.addError('oh no', StackTrace.current); }); test('forwards addStream() to onAdd() and onError()', () { var sink = _StreamSink( onAdd: expectAsync1((value) { expect(value, equals(123)); }, count: 1), onError: expectAsync2((error, [stackTrace]) { expect(error, equals('oh no')); expect(stackTrace, isA()); }), ); var controller = StreamController(); sink.addStream(controller.stream); controller.add(123); controller.addError('oh no', StackTrace.current); }); test('addStream() returns once the stream closes', () async { var sink = _StreamSink(); var controller = StreamController(); var addStreamCompleted = false; sink.addStream(controller.stream).then((_) => addStreamCompleted = true); await pumpEventQueue(); expect(addStreamCompleted, isFalse); controller.addError('oh no', StackTrace.current); await pumpEventQueue(); expect(addStreamCompleted, isFalse); controller.close(); await pumpEventQueue(); expect(addStreamCompleted, isTrue); }); test('forwards close() to onClose()', () { var sink = _StreamSink(onClose: expectAsync0(() {})); expect(sink.close(), completes); }); test('onClose() is only invoked once', () { var sink = _StreamSink(onClose: expectAsync0(() {}, count: 1)); expect(sink.close(), completes); expect(sink.close(), completes); expect(sink.close(), completes); }); test('all invocations of close() return the same future', () async { var completer = Completer(); var sink = _StreamSink(onClose: expectAsync0(() => completer.future)); var close1Completed = false; sink.close().then((_) => close1Completed = true); var close2Completed = false; sink.close().then((_) => close2Completed = true); var doneCompleted = false; sink.done.then((_) => doneCompleted = true); await pumpEventQueue(); expect(close1Completed, isFalse); expect(close2Completed, isFalse); expect(doneCompleted, isFalse); completer.complete(); await pumpEventQueue(); expect(close1Completed, isTrue); expect(close2Completed, isTrue); expect(doneCompleted, isTrue); }); test( 'done returns a future that completes once close() completes', () async { var completer = Completer(); var sink = _StreamSink(onClose: expectAsync0(() => completer.future)); var doneCompleted = false; sink.done.then((_) => doneCompleted = true); await pumpEventQueue(); expect(doneCompleted, isFalse); expect(sink.close(), completes); await pumpEventQueue(); expect(doneCompleted, isFalse); completer.complete(); await pumpEventQueue(); expect(doneCompleted, isTrue); }, ); group('during addStream()', () { test('add() throws an error', () { var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0)); sink.addStream(StreamController().stream); expect(() => sink.add(1), throwsStateError); }); test('addError() throws an error', () { var sink = _StreamSink(onError: expectAsync2((_, [__]) {}, count: 0)); sink.addStream(StreamController().stream); expect(() => sink.addError('oh no'), throwsStateError); }); test('addStream() throws an error', () { var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0)); sink.addStream(StreamController().stream); expect(() => sink.addStream(Stream.value(123)), throwsStateError); }); test('close() throws an error', () { var sink = _StreamSink(onClose: expectAsync0(() {}, count: 0)); sink.addStream(StreamController().stream); expect(() => sink.close(), throwsStateError); }); }); group("once it's closed", () { test('add() throws an error', () { var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.add(1), throwsStateError); }); test('addError() throws an error', () { var sink = _StreamSink(onError: expectAsync2((_, [__]) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.addError('oh no'), throwsStateError); }); test('addStream() throws an error', () { var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.addStream(Stream.value(123)), throwsStateError); }); }); }); group('IOSinkBase', () { group('write()', () { test("doesn't call add() for the empty string", () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); sink.write(''); }); test('converts the text to data and passes it to add', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(utf8.encode('hello'))); }), ); sink.write('hello'); }); test('calls Object.toString()', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(utf8.encode('123'))); }), ); sink.write(123); }); test('respects the encoding', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(latin1.encode('Æ'))); }), encoding: latin1, ); sink.write('Æ'); }); test('throws if the sink is closed', () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.write('hello'), throwsStateError); }); }); group('writeAll()', () { test('writes nothing for an empty iterable', () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); sink.writeAll([]); }); test('writes each object in the iterable', () async { var chunks = >[]; var sink = _IOSink( onAdd: expectAsync1((data) { chunks.add(data); }, count: 3), ); sink.writeAll(['hello', null, 123]); expect(chunks, equals(['hello', 'null', '123'].map(utf8.encode))); }); test('writes separators between each object', () async { var chunks = >[]; var sink = _IOSink( onAdd: expectAsync1((data) { chunks.add(data); }, count: 5), ); sink.writeAll(['hello', null, 123], '/'); expect( chunks, equals(['hello', '/', 'null', '/', '123'].map(utf8.encode)), ); }); test('throws if the sink is closed', () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.writeAll(['hello']), throwsStateError); }); }); group('writeln()', () { test('only writes a newline by default', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(utf8.encode('\n'))); }, count: 1), ); sink.writeln(); }); test('writes the object followed by a newline', () async { var chunks = >[]; var sink = _IOSink( onAdd: expectAsync1((data) { chunks.add(data); }, count: 2), ); sink.writeln(123); expect(chunks, equals(['123', '\n'].map(utf8.encode))); }); test('throws if the sink is closed', () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.writeln(), throwsStateError); }); }); group('writeCharCode()', () { test('writes the character code', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(utf8.encode('A'))); }), ); sink.writeCharCode(letterA); }); test('respects the encoding', () async { var sink = _IOSink( onAdd: expectAsync1((data) { expect(data, equals(latin1.encode('Æ'))); }), encoding: latin1, ); sink.writeCharCode('Æ'.runes.first); }); test('throws if the sink is closed', () async { var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0)); expect(sink.close(), completes); expect(() => sink.writeCharCode(letterA), throwsStateError); }); }); group('flush()', () { test('returns a future that completes when onFlush() is done', () async { var completer = Completer(); var sink = _IOSink(onFlush: expectAsync0(() => completer.future)); var flushDone = false; sink.flush().then((_) => flushDone = true); await pumpEventQueue(); expect(flushDone, isFalse); completer.complete(); await pumpEventQueue(); expect(flushDone, isTrue); }); test('does nothing after close() is called', () { var sink = _IOSink(onFlush: expectAsync0(Future.value, count: 0)); expect(sink.close(), completes); expect(sink.flush(), completes); }); test("can't be called during addStream()", () { var sink = _IOSink(onFlush: expectAsync0(Future.value, count: 0)); sink.addStream(StreamController>().stream); expect(() => sink.flush(), throwsStateError); }); test('locks the sink as though a stream was being added', () { var sink = _IOSink( onFlush: expectAsync0(() => Completer().future), ); sink.flush(); expect(() => sink.add([0]), throwsStateError); expect(() => sink.addError('oh no'), throwsStateError); expect(() => sink.addStream(const Stream.empty()), throwsStateError); expect(() => sink.flush(), throwsStateError); expect(() => sink.close(), throwsStateError); }); }); }); } /// A subclass of [StreamSinkBase] that takes all the overridable methods as /// callbacks, for ease of testing. class _StreamSink extends StreamSinkBase { final void Function(int value) _onAdd; final void Function(Object error, [StackTrace? stackTrace]) _onError; final FutureOr Function() _onClose; _StreamSink({ void Function(int value)? onAdd, void Function(Object error, [StackTrace? stackTrace])? onError, FutureOr Function()? onClose, }) : _onAdd = onAdd ?? ((_) {}), _onError = onError ?? ((_, [__]) {}), _onClose = onClose ?? (() {}); @override void onAdd(int value) { _onAdd(value); } @override void onError(Object error, [StackTrace? stackTrace]) { _onError(error, stackTrace); } @override FutureOr onClose() => _onClose(); } /// A subclass of [IOSinkBase] that takes all the overridable methods as /// callbacks, for ease of testing. class _IOSink extends IOSinkBase { final void Function(List value) _onAdd; final void Function(Object error, [StackTrace? stackTrace]) _onError; final FutureOr Function() _onClose; final Future Function() _onFlush; _IOSink({ void Function(List value)? onAdd, void Function(Object error, [StackTrace? stackTrace])? onError, FutureOr Function()? onClose, Future Function()? onFlush, Encoding encoding = utf8, }) : _onAdd = onAdd ?? ((_) {}), _onError = onError ?? ((_, [__]) {}), _onClose = onClose ?? (() {}), _onFlush = onFlush ?? Future.value, super(encoding); @override void onAdd(List value) { _onAdd(value); } @override void onError(Object error, [StackTrace? stackTrace]) { _onError(error, stackTrace); } @override FutureOr onClose() => _onClose(); @override Future onFlush() => _onFlush(); }