From dfe53225ae308a2486f6b3e30d0d32ed40a09aa4 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Sun, 19 May 2019 21:38:25 -0700 Subject: [PATCH] Change implementations to return Uint8List rather than List This is in preparation for https://github.com/dart-lang/sdk/issues/36900 --- packages/file/CHANGELOG.md | 4 ++ packages/file/lib/src/backends/chroot.dart | 1 + .../lib/src/backends/chroot/chroot_file.dart | 6 +-- .../lib/src/backends/memory/memory_file.dart | 20 +++++---- .../file/lib/src/backends/memory/node.dart | 22 ++++++++-- .../src/backends/record_replay/codecs.dart | 13 +++++- .../record_replay/recording_file.dart | 19 +++++---- .../backends/record_replay/replay_file.dart | 16 ++++---- .../replay_random_access_file.dart | 5 ++- .../lib/src/forwarding/forwarding_file.dart | 41 +++++++++++++++++-- packages/file/pubspec.yaml | 2 +- packages/file/test/common_tests.dart | 10 +++++ .../file/test/record_replay_matchers.dart | 21 +++------- packages/file/test/recording_test.dart | 17 ++++---- packages/file/test/replay_test.dart | 2 +- 15 files changed, 132 insertions(+), 67 deletions(-) diff --git a/packages/file/CHANGELOG.md b/packages/file/CHANGELOG.md index 740dfdf..5c28d0d 100644 --- a/packages/file/CHANGELOG.md +++ b/packages/file/CHANGELOG.md @@ -1,3 +1,7 @@ +#### 5.0.8 + +* Return Uint8List rather than List. + #### 5.0.7 * Dart 2 fixes for `RecordingProxyMixin` and `ReplayProxyMixin`. diff --git a/packages/file/lib/src/backends/chroot.dart b/packages/file/lib/src/backends/chroot.dart index e4355be..72ce3ca 100644 --- a/packages/file/lib/src/backends/chroot.dart +++ b/packages/file/lib/src/backends/chroot.dart @@ -6,6 +6,7 @@ library file.src.backends.chroot; import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:file/src/common.dart' as common; diff --git a/packages/file/lib/src/backends/chroot/chroot_file.dart b/packages/file/lib/src/backends/chroot/chroot_file.dart index 79b361e..ea65c38 100644 --- a/packages/file/lib/src/backends/chroot/chroot_file.dart +++ b/packages/file/lib/src/backends/chroot/chroot_file.dart @@ -251,7 +251,7 @@ class _ChrootFile extends _ChrootFileSystemEntity getDelegate(followLinks: true).openSync(mode: mode); @override - Stream> openRead([int start, int end]) => + Stream openRead([int start, int end]) => getDelegate(followLinks: true).openRead(start, end); @override @@ -262,11 +262,11 @@ class _ChrootFile extends _ChrootFileSystemEntity getDelegate(followLinks: true).openWrite(mode: mode, encoding: encoding); @override - Future> readAsBytes() => + Future readAsBytes() => getDelegate(followLinks: true).readAsBytes(); @override - List readAsBytesSync() => + Uint8List readAsBytesSync() => getDelegate(followLinks: true).readAsBytesSync(); @override diff --git a/packages/file/lib/src/backends/memory/memory_file.dart b/packages/file/lib/src/backends/memory/memory_file.dart index 73899dd..019c4be 100644 --- a/packages/file/lib/src/backends/memory/memory_file.dart +++ b/packages/file/lib/src/backends/memory/memory_file.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' show min; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:file/src/common.dart' as common; @@ -176,18 +177,18 @@ class MemoryFile extends MemoryFileSystemEntity implements File { throw new UnimplementedError('TODO'); @override - Stream> openRead([int start, int end]) { + Stream openRead([int start, int end]) { try { FileNode node = resolvedBacking; - List content = node.content; + Uint8List content = node.content; if (start != null) { content = end == null ? content.sublist(start) : content.sublist(start, min(end, content.length)); } - return new Stream>.fromIterable(>[content]); + return new Stream.fromIterable([content]); } catch (e) { - return new Stream>.fromFuture(new Future>.error(e)); + return new Stream.fromFuture(new Future.error(e)); } } @@ -204,10 +205,11 @@ class MemoryFile extends MemoryFileSystemEntity implements File { } @override - Future> readAsBytes() async => readAsBytesSync(); + Future readAsBytes() async => readAsBytesSync(); @override - List readAsBytesSync() => (resolvedBacking as FileNode).content; + Uint8List readAsBytesSync() => + Uint8List.fromList((resolvedBacking as FileNode).content); @override Future readAsString({Encoding encoding: utf8}) async => @@ -248,7 +250,7 @@ class MemoryFile extends MemoryFileSystemEntity implements File { } FileNode node = _resolvedBackingOrCreate; _truncateIfNecessary(node, mode); - node.content.addAll(bytes); + node.write(bytes); node.touch(); } @@ -278,7 +280,7 @@ class MemoryFile extends MemoryFileSystemEntity implements File { void _truncateIfNecessary(FileNode node, io.FileMode mode) { if (mode == io.FileMode.write || mode == io.FileMode.writeOnly) { - node.content.clear(); + node.clear(); } } @@ -402,7 +404,7 @@ class _FileSink implements io.IOSink { void _addData(List data) { _pendingWrites = _pendingWrites.then((FileNode node) { - node.content.addAll(data); + node.write(data); return node; }); } diff --git a/packages/file/lib/src/backends/memory/node.dart b/packages/file/lib/src/backends/memory/node.dart index b4073a5..65a2539 100644 --- a/packages/file/lib/src/backends/memory/node.dart +++ b/packages/file/lib/src/backends/memory/node.dart @@ -2,6 +2,8 @@ // 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:typed_data'; + import 'package:file/file.dart'; import 'package:file/src/io.dart' as io; @@ -222,7 +224,8 @@ class RootNode extends DirectoryNode { /// Class that represents the backing for an in-memory regular file. class FileNode extends RealNode { /// File contents in bytes. - List content = []; + Uint8List get content => _content; + Uint8List _content = Uint8List(0); /// Constructs a new [FileNode] as a child of the specified [parent]. FileNode(DirectoryNode parent) : super(parent); @@ -231,7 +234,20 @@ class FileNode extends RealNode { io.FileSystemEntityType get type => io.FileSystemEntityType.file; @override - int get size => content.length; + int get size => _content.length; + + /// Appends the specified bytes to the end of this node's [content]. + void write(List bytes) { + Uint8List existing = _content; + _content = Uint8List(existing.length + bytes.length); + _content.setRange(0, existing.length, existing); + _content.setRange(existing.length, _content.length, bytes); + } + + /// Clears the [content] of the node. + void clear() { + _content = Uint8List(0); + } /// Copies data from [source] into this node. The [modified] and [changed] /// fields will be reset as opposed to copied to indicate that this file @@ -240,7 +256,7 @@ class FileNode extends RealNode { modified = changed = new DateTime.now().millisecondsSinceEpoch; accessed = source.accessed; mode = source.mode; - content = new List.from(source.content); + _content = Uint8List.fromList(source.content); } } diff --git a/packages/file/lib/src/backends/record_replay/codecs.dart b/packages/file/lib/src/backends/record_replay/codecs.dart index 67236e7..bc1b30a 100644 --- a/packages/file/lib/src/backends/record_replay/codecs.dart +++ b/packages/file/lib/src/backends/record_replay/codecs.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io' show systemEncoding; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:path/path.dart' as path; @@ -455,6 +456,14 @@ class Listify extends Converter> { List convert(T input) => [input]; } +class Uint8ListToPlainList extends Converter> { + /// Creates a new [Uint8ListToPlainList] + const Uint8ListToPlainList(); + + @override + List convert(Uint8List list) => List.from(list); +} + /// Revives a [Directory] entity reference into a [ReplayDirectory]. class ReviveDirectory extends Converter { final ReplayFileSystemImpl _fileSystem; @@ -571,7 +580,7 @@ class ToStream extends Converter, Stream> { /// Converts a blob reference (serialized as a [String] of the form /// `!`) into a byte list. -class BlobToBytes extends Converter> { +class BlobToBytes extends Converter { final ReplayFileSystemImpl _fileSystem; /// Creates a new [BlobToBytes] that will use the specified file system's @@ -579,7 +588,7 @@ class BlobToBytes extends Converter> { const BlobToBytes(this._fileSystem); @override - List convert(String input) { + Uint8List convert(String input) { assert(input.startsWith('!')); String basename = input.substring(1); String dirname = _fileSystem.recording.path; diff --git a/packages/file/lib/src/backends/record_replay/recording_file.dart b/packages/file/lib/src/backends/record_replay/recording_file.dart index 4400a96..03ccd56 100644 --- a/packages/file/lib/src/backends/record_replay/recording_file.dart +++ b/packages/file/lib/src/backends/record_replay/recording_file.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:file/file.dart'; @@ -89,11 +90,11 @@ class RecordingFile extends RecordingFileSystemEntity implements File { RandomAccessFile _openSync({FileMode mode: FileMode.read}) => _wrapRandomAccessFile(delegate.openSync(mode: mode)); - StreamReference> _openRead([int start, int end]) { - return new _BlobStreamReference>( + StreamReference _openRead([int start, int end]) { + return new _BlobStreamReference( file: _newRecordingFile(), stream: delegate.openRead(start, end), - writer: (File file, List bytes) { + writer: (File file, Uint8List bytes) { file.writeAsBytesSync(bytes, mode: FileMode.append, flush: true); }, ); @@ -106,21 +107,21 @@ class RecordingFile extends RecordingFileSystemEntity implements File { ); } - FutureReference> _readAsBytes() { - return new _BlobFutureReference>( + FutureReference _readAsBytes() { + return new _BlobFutureReference( file: _newRecordingFile(), future: delegate.readAsBytes(), - writer: (File file, List bytes) async { + writer: (File file, Uint8List bytes) async { await file.writeAsBytes(bytes, flush: true); }, ); } - ResultReference> _readAsBytesSync() { - return new _BlobReference>( + ResultReference _readAsBytesSync() { + return new _BlobReference( file: _newRecordingFile(), value: delegate.readAsBytesSync(), - writer: (File file, List bytes) { + writer: (File file, Uint8List bytes) { file.writeAsBytesSync(bytes, flush: true); }, ); diff --git a/packages/file/lib/src/backends/record_replay/replay_file.dart b/packages/file/lib/src/backends/record_replay/replay_file.dart index 9a21416..d40e046 100644 --- a/packages/file/lib/src/backends/record_replay/replay_file.dart +++ b/packages/file/lib/src/backends/record_replay/replay_file.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/file.dart'; @@ -20,10 +21,11 @@ class ReplayFile extends ReplayFileSystemEntity implements File { Converter reviveFile = new ReviveFile(fileSystem); Converter> reviveFileAsFuture = reviveFile.fuse(const ToFuture()); - Converter> blobToBytes = new BlobToBytes(fileSystem); - Converter>> blobToBytesFuture = - blobToBytes.fuse(const ToFuture>()); - Converter blobToString = blobToBytes.fuse(utf8.decoder); + Converter blobToBytes = new BlobToBytes(fileSystem); + Converter> blobToBytesFuture = + blobToBytes.fuse(const ToFuture()); + Converter blobToString = + blobToBytes.fuse(const Uint8ListToPlainList()).fuse(utf8.decoder); Converter> blobToStringFuture = blobToString.fuse(const ToFuture()); Converter reviveRandomAccessFile = @@ -36,9 +38,9 @@ class ReplayFile extends ReplayFileSystemEntity implements File { blobToString.fuse(lineSplitter); Converter>> blobToLinesFuture = blobToLines.fuse(const ToFuture>()); - Converter>> blobToByteStream = blobToBytes - .fuse(const Listify>()) - .fuse(const ToStream>()); + Converter> blobToByteStream = blobToBytes + .fuse(const Listify()) + .fuse(const ToStream()); Converter> reviveDateTime = DateTimeCodec.deserialize.fuse(const ToFuture()); diff --git a/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart b/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart index c1219b4..9bfb4b3 100644 --- a/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart +++ b/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/file.dart'; @@ -29,8 +30,8 @@ class ReplayRandomAccessFile extends Object #closeSync: const Passthrough(), #readByte: const ToFuture(), #readByteSync: const Passthrough(), - #read: const ToFuture>(), - #readSync: const Passthrough>(), + #read: const ToFuture(), + #readSync: const Passthrough(), #readInto: const ToFuture(), #readIntoSync: const Passthrough(), #writeByte: reviveRandomAccessFileAsFuture, diff --git a/packages/file/lib/src/forwarding/forwarding_file.dart b/packages/file/lib/src/forwarding/forwarding_file.dart index ef99d92..3d76af0 100644 --- a/packages/file/lib/src/forwarding/forwarding_file.dart +++ b/packages/file/lib/src/forwarding/forwarding_file.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/src/io.dart' as io; import 'package:file/file.dart'; @@ -71,8 +72,8 @@ abstract class ForwardingFile delegate.openSync(mode: mode); @override - Stream> openRead([int start, int end]) => - delegate.openRead(start, end); + Stream openRead([int start, int end]) => + delegate.openRead(start, end).transform(const _ToUint8List()); @override IOSink openWrite({ @@ -82,10 +83,14 @@ abstract class ForwardingFile delegate.openWrite(mode: mode, encoding: encoding); @override - Future> readAsBytes() => delegate.readAsBytes(); + Future readAsBytes() { + return delegate.readAsBytes().then((List bytes) { + return Uint8List.fromList(bytes); + }); + } @override - List readAsBytesSync() => delegate.readAsBytesSync(); + Uint8List readAsBytesSync() => Uint8List.fromList(delegate.readAsBytesSync()); @override Future readAsString({Encoding encoding: utf8}) => @@ -151,3 +156,31 @@ abstract class ForwardingFile flush: flush, ); } + +class _ToUint8List extends Converter, Uint8List> { + const _ToUint8List(); + + @override + Uint8List convert(List input) => Uint8List.fromList(input); + + @override + Sink> startChunkedConversion(Sink sink) { + return _Uint8ListConversionSink(sink); + } +} + +class _Uint8ListConversionSink implements Sink> { + const _Uint8ListConversionSink(this._target); + + final Sink _target; + + @override + void add(List data) { + _target.add(Uint8List.fromList(data)); + } + + @override + void close() { + _target.close(); + } +} diff --git a/packages/file/pubspec.yaml b/packages/file/pubspec.yaml index 2402f8c..dc7ccdd 100644 --- a/packages/file/pubspec.yaml +++ b/packages/file/pubspec.yaml @@ -1,5 +1,5 @@ name: file -version: 5.0.7 +version: 5.0.8 authors: - Matan Lurey - Yegor Jbanov diff --git a/packages/file/test/common_tests.dart b/packages/file/test/common_tests.dart index 23923eb..281ac13 100644 --- a/packages/file/test/common_tests.dart +++ b/packages/file/test/common_tests.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io' as io; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:file_testing/file_testing.dart'; @@ -2394,6 +2395,15 @@ void runCommonTests( File f = fs.file(ns('/foo'))..createSync(); expect(f.readAsBytesSync(), isEmpty); }); + + test('returns a copy, not a view, of the file content', () { + File f = fs.file(ns('/foo'))..createSync(); + f.writeAsBytesSync([1, 2, 3, 4]); + List result = f.readAsBytesSync(); + expect(result, [1, 2, 3, 4]); + result[0] = 10; + expect(f.readAsBytesSync(), [1, 2, 3, 4]); + }); }); group('readAsString', () { diff --git a/packages/file/test/record_replay_matchers.dart b/packages/file/test/record_replay_matchers.dart index 34c5387..952e1e8 100644 --- a/packages/file/test/record_replay_matchers.dart +++ b/packages/file/test/record_replay_matchers.dart @@ -14,9 +14,9 @@ const Map _kTypeDescriptions = const { }; const Map _kTypeMatchers = const { - MethodEvent: const isInstanceOf>(), - PropertyGetEvent: const isInstanceOf>(), - PropertySetEvent: const isInstanceOf>(), + MethodEvent: const TypeMatcher>(), + PropertyGetEvent: const TypeMatcher>(), + PropertySetEvent: const TypeMatcher>(), }; /// Returns a matcher that will match against a [MethodEvent]. @@ -49,13 +49,10 @@ PropertyGet getsProperty([dynamic name]) => new PropertyGet._(name); /// scope of the match (e.g. by property value, target object, etc). PropertySet setsProperty([dynamic name]) => new PropertySet._(name); -/// A matcher that successfully matches against an instance of -/// [NoMatchingInvocationError]. -const Matcher isNoMatchingInvocationError = const _NoMatchingInvocationError(); - /// A matcher that successfully matches against a future or function /// that throws a [NoMatchingInvocationError]. -Matcher throwsNoMatchingInvocationError = throwsA(isNoMatchingInvocationError); +Matcher throwsNoMatchingInvocationError = + throwsA(const TypeMatcher()); /// Base class for matchers that match against generic [InvocationEvent] /// instances. @@ -583,11 +580,3 @@ class _SetValue extends Matcher { return _matcher.describe(description); } } - -class _NoMatchingInvocationError extends TypeMatcher { - const _NoMatchingInvocationError() : super("NoMatchingInvocationError"); - - @override - bool matches(dynamic item, Map matchState) => - item is NoMatchingInvocationError; -} diff --git a/packages/file/test/recording_test.dart b/packages/file/test/recording_test.dart index 03286fe..cb9dc46 100644 --- a/packages/file/test/recording_test.dart +++ b/packages/file/test/recording_test.dart @@ -9,7 +9,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file/record_replay.dart'; import 'package:file/src/backends/record_replay/codecs.dart'; -import 'package:file/src/backends/record_replay/common.dart'; +import 'package:file/src/backends/record_replay/common.dart' hide TypeMatcher; import 'package:file/src/backends/record_replay/events.dart'; import 'package:file/src/backends/record_replay/mutable_recording.dart'; import 'package:file/src/backends/record_replay/recording_proxy_mixin.dart'; @@ -157,7 +157,7 @@ void main() { test('awaitsPendingResultsIndefinitelyByDefault', () async { rc.veryLongFutureMethod(); // ignore: unawaited_futures expect(recording.flush().timeout(const Duration(milliseconds: 50)), - throwsTimeoutException); + throwsA(const TypeMatcher())); }); test('succeedsIfAwaitPendingResultsThatComplete', () async { @@ -398,7 +398,7 @@ void main() { events[0], getsProperty('path') .on(fs) - .withResult(const isInstanceOf()), + .withResult(const TypeMatcher()), ); }); @@ -613,7 +613,7 @@ void main() { recording.events, contains(invokesMethod('openRead') .on(isFile) - .withPositionalArguments([null, null]) + .withPositionalArguments([null, null]) .withNoNamedArguments() .withResult(isList))); await recording.flush(); @@ -625,7 +625,7 @@ void main() { containsPair('type', 'invoke'), containsPair('method', 'openRead'), containsPair('object', matches(r'^RecordingFile@[0-9]+$')), - containsPair('positionalArguments', [null, null]), + containsPair('positionalArguments', [null, null]), containsPair('namedArguments', isEmpty), containsPair('result', matches(r'^![0-9]+.foo$')), )); @@ -783,7 +783,8 @@ void main() { containsPair('object', matches(r'^RecordingFile@[0-9]+$')), containsPair('positionalArguments', isEmpty), containsPair('result', matches(r'^![0-9]+.foo$')), - containsPair('namedArguments', {'encoding': 'utf-8'}), + containsPair( + 'namedArguments', {'encoding': 'utf-8'}), )); File file = _getRecordingFile(recording, manifest[1]['result']); expect(file, exists); @@ -932,7 +933,3 @@ class _FakeStopwatch implements Stopwatch { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } - -/// Successfully matches against a function that throws a [TimeoutException]. -Matcher throwsTimeoutException = - throwsA(const isInstanceOf()); diff --git a/packages/file/test/replay_test.dart b/packages/file/test/replay_test.dart index b059356..37cb261 100644 --- a/packages/file/test/replay_test.dart +++ b/packages/file/test/replay_test.dart @@ -204,4 +204,4 @@ void main() { } /// Successfully matches against an instance of [Future]. -const Matcher isFuture = const isInstanceOf>(); +const Matcher isFuture = const TypeMatcher>();