diff --git a/lib/src/backends/record_replay/codecs.dart b/lib/src/backends/record_replay/codecs.dart index 443c33728536f..8b6bb559d4356 100644 --- a/lib/src/backends/record_replay/codecs.dart +++ b/lib/src/backends/record_replay/codecs.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io' show SYSTEM_ENCODING; import 'package:file/file.dart'; import 'package:path/path.dart' as path; @@ -14,7 +15,9 @@ import 'replay_directory.dart'; import 'replay_file.dart'; import 'replay_file_stat.dart'; import 'replay_file_system.dart'; +import 'replay_io_sink.dart'; import 'replay_link.dart'; +import 'replay_random_access_file.dart'; import 'result_reference.dart'; /// Converter that leaves object untouched. @@ -22,7 +25,10 @@ const Converter kPassthrough = const _PassthroughConverter(); /// Converter that will turn an object into a [Future] of that object. const Converter kFutureReviver = - const _FutureDecoder(); + const _FutureReviver(); + +/// Converter that will convert an [Iterable] into a [Stream]. +Converter kStreamReviver = const _StreamReviver(); /// Converter that will deserialize a [DateTime]. const Converter kDateTimeReviver = _DateTimeCodec.kDecoder; @@ -38,17 +44,50 @@ const Converter kEntityTypeReviver = const Converter kPathContextReviver = _PathContextCodec.kDecoder; +/// Converter that will deserialize a [Uri]. +const Converter kUriReviver = _UriCodec.kDecoder; + +/// Converter that will deserialize a [Encoding]. +const Converter kEncodingReviver = _EncodingCodec.kDecoder; + +/// Converter that will deserialize a [FileSystemEvent]. +const Converter kFileSystemEventReviver = + _FileSystemEventCodec.kDecoder; + +/// Converter that will deserialize each element of a [List] by delegating to +/// the specified [elementReviver]. +Converter listReviver( + Converter elementReviver) => + new _ListReviver(elementReviver); + +/// Converter that will deserialize a blob file reference into the file's bytes. +Converter blobReviver(ReplayFileSystemImpl fileSystem) => + new _BlobReviver(fileSystem); + /// Converter that will deserialize a [ReplayDirectory]. Converter directoryReviver(ReplayFileSystemImpl fileSystem) => - new _DirectoryDecoder(fileSystem); + new _DirectoryReviver(fileSystem); /// Converter that will deserialize a [ReplayFile]. Converter fileReviver(ReplayFileSystemImpl fileSystem) => - new _FileDecoder(fileSystem); + new _FileReviver(fileSystem); /// Converter that will deserialize a [ReplayLink]. Converter linkReviver(ReplayFileSystemImpl fileSystem) => - new _LinkDecoder(fileSystem); + new _LinkReviver(fileSystem); + +/// Converter that will deserialize an arbitrary [FileSystemEntity]. +Converter entityReviver(ReplayFileSystemImpl fileSystem) => + new _FileSystemEntityReviver(fileSystem); + +/// Converter that will deserialize a [ReplayRandomAccessFile]. +Converter randomAccessFileReviver( + ReplayFileSystemImpl fileSystem) => + new _RandomAccessFileReviver(fileSystem); + +/// Converter that will deserialize a [ReplayRandomAccessFile]. +Converter ioSinkReviver(ReplayFileSystemImpl fileSystem) => + new _IOSinkReviver(fileSystem); /// Encodes an arbitrary [object] into a JSON-ready representation (a number, /// boolean, string, null, list, or map). @@ -87,16 +126,16 @@ class _GenericEncoder extends Converter { const TypeMatcher>(): const _MapEncoder(), const TypeMatcher(): const _SymbolEncoder(), const TypeMatcher(): _DateTimeCodec.kEncoder, - const TypeMatcher(): const _ToStringEncoder(), + const TypeMatcher(): _UriCodec.kEncoder, const TypeMatcher(): _PathContextCodec.kEncoder, const TypeMatcher>(): const _ResultEncoder(), const TypeMatcher>(): const _EventEncoder(), const TypeMatcher(): const _ReplayAwareEncoder(), - const TypeMatcher(): const _EncodingEncoder(), + const TypeMatcher(): _EncodingCodec.kEncoder, const TypeMatcher(): const _FileModeEncoder(), const TypeMatcher(): _FileStatCodec.kEncoder, const TypeMatcher(): _EntityTypeCodec.kEncoder, - const TypeMatcher(): const _FileSystemEventEncoder(), + const TypeMatcher(): _FileSystemEventCodec.kEncoder, }; /// Default encoder (used for types not covered in [_encoders]). @@ -163,10 +202,13 @@ class _SymbolEncoder extends Converter { class _DateTimeCodec extends Codec { const _DateTimeCodec(); - static int _encode(DateTime input) => input.millisecondsSinceEpoch; + static int _encode(DateTime input) => input?.millisecondsSinceEpoch; - static DateTime _decode(int input) => - new DateTime.fromMillisecondsSinceEpoch(input); + static DateTime _decode(int input) { + return input == null + ? null + : new DateTime.fromMillisecondsSinceEpoch(input); + } static const Converter kEncoder = const _ForwardingConverter(_encode); @@ -181,11 +223,24 @@ class _DateTimeCodec extends Codec { Converter get decoder => kDecoder; } -class _ToStringEncoder extends Converter { - const _ToStringEncoder(); +class _UriCodec extends Codec { + const _UriCodec(); + + static String _encode(Uri input) => input.toString(); + + static Uri _decode(String input) => Uri.parse(input); + + static const Converter kEncoder = + const _ForwardingConverter(_encode); + + static const Converter kDecoder = + const _ForwardingConverter(_decode); @override - String convert(Object input) => input.toString(); + Converter get encoder => kEncoder; + + @override + Converter get decoder => kDecoder; } class _PathContextCodec extends Codec> { @@ -246,11 +301,31 @@ class _ReplayAwareEncoder extends Converter { String convert(ReplayAware input) => input.identifier; } -class _EncodingEncoder extends Converter { - const _EncodingEncoder(); +class _EncodingCodec extends Codec { + const _EncodingCodec(); + + static String _encode(Encoding input) => input.name; + + static Encoding _decode(String input) { + if (input == 'system') { + return SYSTEM_ENCODING; + } else if (input != null) { + return Encoding.getByName(input); + } + return null; + } + + static const Converter kEncoder = + const _ForwardingConverter(_encode); + + static const Converter kDecoder = + const _ForwardingConverter(_decode); + + @override + Converter get encoder => kEncoder; @override - String convert(Encoding input) => input.name; + Converter get decoder => kDecoder; } class _FileModeEncoder extends Converter { @@ -332,46 +407,143 @@ class _EntityTypeCodec extends Codec { Converter get decoder => kDecoder; } -class _FileSystemEventEncoder - extends Converter> { - const _FileSystemEventEncoder(); +class _FileSystemEventCodec + extends Codec> { + const _FileSystemEventCodec(); - @override - Map convert(FileSystemEvent input) { + static Map _encode(FileSystemEvent input) { return { 'type': input.type, 'path': input.path, + 'isDirectory': input.isDirectory, }; } + + static FileSystemEvent _decode(Map input) => + new _FileSystemEvent(input); + + static const Converter> kEncoder = + const _ForwardingConverter>(_encode); + + static const Converter, FileSystemEvent> kDecoder = + const _ForwardingConverter, FileSystemEvent>(_decode); + + @override + Converter> get encoder => kEncoder; + + @override + Converter, FileSystemEvent> get decoder => kDecoder; +} + +class _FileSystemEvent implements FileSystemEvent { + final Map _data; + + const _FileSystemEvent(this._data); + + @override + int get type => _data['type']; + + @override + String get path => _data['path']; + + @override + bool get isDirectory => _data['isDirectory']; } -class _FutureDecoder extends Converter> { - const _FutureDecoder(); +class _FutureReviver extends Converter> { + const _FutureReviver(); @override Future convert(T input) async => input; } -class _DirectoryDecoder extends Converter { +class _DirectoryReviver extends Converter { final ReplayFileSystemImpl fileSystem; - const _DirectoryDecoder(this.fileSystem); + const _DirectoryReviver(this.fileSystem); @override Directory convert(String input) => new ReplayDirectory(fileSystem, input); } -class _FileDecoder extends Converter { +class _FileReviver extends Converter { final ReplayFileSystemImpl fileSystem; - const _FileDecoder(this.fileSystem); + const _FileReviver(this.fileSystem); @override File convert(String input) => new ReplayFile(fileSystem, input); } -class _LinkDecoder extends Converter { +class _LinkReviver extends Converter { final ReplayFileSystemImpl fileSystem; - const _LinkDecoder(this.fileSystem); + const _LinkReviver(this.fileSystem); @override Link convert(String input) => new ReplayLink(fileSystem, input); } + +class _FileSystemEntityReviver extends Converter { + final ReplayFileSystemImpl fileSystem; + const _FileSystemEntityReviver(this.fileSystem); + + @override + FileSystemEntity convert(String input) { + if (input.contains('Directory')) { + return new ReplayDirectory(fileSystem, input); + } else if (input.contains('File')) { + return new ReplayFile(fileSystem, input); + } else { + return new ReplayLink(fileSystem, input); + } + } +} + +class _RandomAccessFileReviver extends Converter { + final ReplayFileSystemImpl fileSystem; + const _RandomAccessFileReviver(this.fileSystem); + + @override + RandomAccessFile convert(String input) => + new ReplayRandomAccessFile(fileSystem, input); +} + +class _IOSinkReviver extends Converter { + final ReplayFileSystemImpl fileSystem; + const _IOSinkReviver(this.fileSystem); + + @override + IOSink convert(String input) => new ReplayIOSink(fileSystem, input); +} + +class _ListReviver extends Converter, List> { + final Converter elementReviver; + + const _ListReviver(this.elementReviver); + + @override + List convert(Iterable input) => + input.map(elementReviver.convert).toList(); +} + +class _StreamReviver extends Converter, Stream> { + const _StreamReviver(); + + @override + Stream convert(List input) { + return new Stream.fromIterable(input); + } +} + +class _BlobReviver extends Converter> { + final ReplayFileSystemImpl fileSystem; + const _BlobReviver(this.fileSystem); + + @override + List convert(String input) { + assert(input.startsWith('!')); + String basename = input.substring(1); + String dirname = fileSystem.recording.path; + String path = fileSystem.recording.fileSystem.path.join(dirname, basename); + File file = fileSystem.recording.fileSystem.file(path); + return file.readAsBytesSync(); + } +} diff --git a/lib/src/backends/record_replay/proxy.dart b/lib/src/backends/record_replay/proxy.dart index 2266baa09119d..7c0f83fdc1027 100644 --- a/lib/src/backends/record_replay/proxy.dart +++ b/lib/src/backends/record_replay/proxy.dart @@ -43,6 +43,7 @@ class MethodProxy extends Object implements Function { } } +// TODO(tvolkert): remove (https://github.com/dart-lang/sdk/issues/28706) class _MethodInvocationProxy extends Invocation { _MethodInvocationProxy( this.memberName, diff --git a/lib/src/backends/record_replay/replay_directory.dart b/lib/src/backends/record_replay/replay_directory.dart index abac3e1ee7c1f..75e3b1849437e 100644 --- a/lib/src/backends/record_replay/replay_directory.dart +++ b/lib/src/backends/record_replay/replay_directory.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; +import 'codecs.dart'; import 'replay_file_system.dart'; import 'replay_file_system_entity.dart'; @@ -15,14 +16,24 @@ class ReplayDirectory extends ReplayFileSystemEntity implements Directory { /// Creates a new `ReplayDirectory`. ReplayDirectory(ReplayFileSystemImpl fileSystem, String identifier) : super(fileSystem, identifier) { - // TODO(tvolkert): fill in resurrectors + Converter convertThis = directoryReviver(fileSystem); + Converter convertFutureThis = + convertThis.fuse(kFutureReviver); + methods.addAll(>{ - #create: null, - #createSync: null, - #createTemp: null, - #createTempSync: null, - #list: null, - #listSync: null, + #rename: convertFutureThis, + #renameSync: convertThis, + #delete: convertFutureThis, + #create: convertFutureThis, + #createSync: kPassthrough, + #createTemp: convertFutureThis, + #createTempSync: convertThis, + #list: listReviver(entityReviver(fileSystem)).fuse(kStreamReviver), + #listSync: listReviver(entityReviver(fileSystem)), + }); + + properties.addAll(>{ + #absolute: convertThis, }); } } diff --git a/lib/src/backends/record_replay/replay_file.dart b/lib/src/backends/record_replay/replay_file.dart index 931f5a1d7ce55..149162d0fd5d7 100644 --- a/lib/src/backends/record_replay/replay_file.dart +++ b/lib/src/backends/record_replay/replay_file.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; +import 'codecs.dart'; import 'replay_file_system.dart'; import 'replay_file_system_entity.dart'; @@ -15,30 +16,40 @@ class ReplayFile extends ReplayFileSystemEntity implements File { /// Creates a new `ReplayFile`. ReplayFile(ReplayFileSystemImpl fileSystem, String identifier) : super(fileSystem, identifier) { - // TODO(tvolkert): fill in resurrectors + Converter convertThis = fileReviver(fileSystem); + Converter convertFutureThis = + convertThis.fuse(kFutureReviver); + methods.addAll(>{ - #create: null, - #createSync: null, - #copy: null, - #copySync: null, - #length: null, - #lengthSync: null, - #lastModified: null, - #lastModifiedSync: null, - #open: null, - #openSync: null, - #openRead: null, - #openWrite: null, - #readAsBytes: null, - #readAsBytesSync: null, - #readAsString: null, - #readAsStringSync: null, - #readAsLines: null, - #readAsLinesSync: null, - #writeAsBytes: null, - #writeAsBytesSync: null, - #writeAsString: null, - #writeAsStringSync: null, + #rename: convertFutureThis, + #renameSync: convertThis, + #delete: convertFutureThis, + #create: convertFutureThis, + #createSync: kPassthrough, + #copy: convertFutureThis, + #copySync: convertThis, + #length: kFutureReviver, + #lengthSync: kPassthrough, + #lastModified: kDateTimeReviver.fuse(kFutureReviver), + #lastModifiedSync: kDateTimeReviver, + #open: randomAccessFileReviver(fileSystem).fuse(kFutureReviver), + #openSync: randomAccessFileReviver(fileSystem), + #openRead: kStreamReviver, + #openWrite: ioSinkReviver(fileSystem), + #readAsBytes: blobReviver(fileSystem).fuse(kFutureReviver), + #readAsBytesSync: blobReviver(fileSystem), + #readAsString: kFutureReviver, + #readAsStringSync: kPassthrough, + #readAsLines: kFutureReviver, + #readAsLinesSync: kPassthrough, + #writeAsBytes: convertFutureThis, + #writeAsBytesSync: kPassthrough, + #writeAsString: convertFutureThis, + #writeAsStringSync: kPassthrough, + }); + + properties.addAll(>{ + #absolute: convertThis, }); } } diff --git a/lib/src/backends/record_replay/replay_file_system.dart b/lib/src/backends/record_replay/replay_file_system.dart index df770579eb075..93efa5ec692a8 100644 --- a/lib/src/backends/record_replay/replay_file_system.dart +++ b/lib/src/backends/record_replay/replay_file_system.dart @@ -60,7 +60,7 @@ abstract class ReplayFileSystem extends FileSystem { } List> manifest = new JsonDecoder().convert(manifestFile.readAsStringSync()); - return new ReplayFileSystemImpl(manifest); + return new ReplayFileSystemImpl(recording, manifest); } } @@ -69,7 +69,7 @@ class ReplayFileSystemImpl extends FileSystem with ReplayProxyMixin implements ReplayFileSystem, ReplayAware { /// Creates a new `ReplayFileSystemImpl`. - ReplayFileSystemImpl(this.manifest) { + ReplayFileSystemImpl(this.recording, this.manifest) { methods.addAll(>{ #directory: directoryReviver(this), #file: fileReviver(this), @@ -91,6 +91,9 @@ class ReplayFileSystemImpl extends FileSystem }); } + /// The location of the recording that's driving this file system + final Directory recording; + @override String get identifier => kFileSystemEncodedValue; diff --git a/lib/src/backends/record_replay/replay_file_system_entity.dart b/lib/src/backends/record_replay/replay_file_system_entity.dart index 3dfd39df25f74..da5631daf8121 100644 --- a/lib/src/backends/record_replay/replay_file_system_entity.dart +++ b/lib/src/backends/record_replay/replay_file_system_entity.dart @@ -17,30 +17,24 @@ abstract class ReplayFileSystemEntity extends Object implements FileSystemEntity { /// Creates a new `ReplayFileSystemEntity`. ReplayFileSystemEntity(this.fileSystem, this.identifier) { - // TODO(tvolkert): fill in resurrectors methods.addAll(>{ - #exists: null, - #existsSync: null, - #rename: null, - #renameSync: null, - #resolveSymbolicLinks: null, - #resolveSymbolicLinksSync: null, - #stat: null, - #statSync: null, - #delete: null, - #deleteSync: null, - #watch: null, + #exists: kPassthrough.fuse(kFutureReviver), + #existsSync: kPassthrough, + #resolveSymbolicLinks: kPassthrough.fuse(kFutureReviver), + #resolveSymbolicLinksSync: kPassthrough, + #stat: kFileStatReviver.fuse(kFutureReviver), + #statSync: kFileStatReviver, + #deleteSync: kPassthrough, + #watch: listReviver(kFileSystemEventReviver).fuse(kStreamReviver), }); - // TODO(tvolkert): fill in resurrectors properties.addAll(>{ #path: kPassthrough, - #uri: null, - #isAbsolute: null, - #absolute: null, - #parent: null, - #basename: null, - #dirname: null, + #uri: kUriReviver, + #isAbsolute: kPassthrough, + #parent: directoryReviver(fileSystem), + #basename: kPassthrough, + #dirname: kPassthrough, }); } diff --git a/lib/src/backends/record_replay/replay_io_sink.dart b/lib/src/backends/record_replay/replay_io_sink.dart new file mode 100644 index 0000000000000..b625810eb751b --- /dev/null +++ b/lib/src/backends/record_replay/replay_io_sink.dart @@ -0,0 +1,44 @@ +// 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:convert'; + +import 'package:file/file.dart'; + +import 'codecs.dart'; +import 'replay_file_system.dart'; +import 'replay_proxy_mixin.dart'; + +/// [IOSink] implementation that replays all invocation activity from a prior +/// recording. +class ReplayIOSink extends Object with ReplayProxyMixin implements IOSink { + final ReplayFileSystemImpl _fileSystem; + + /// Creates a new `ReplayIOSink`. + ReplayIOSink(this._fileSystem, this.identifier) { + methods.addAll(>{ + #add: kPassthrough, + #write: kPassthrough, + #writeAll: kPassthrough, + #writeln: kPassthrough, + #writeCharCode: kPassthrough, + #addError: kPassthrough, + #addStream: kFutureReviver, + #flush: kFutureReviver, + #close: kFutureReviver, + }); + + properties.addAll(>{ + #encoding: kEncodingReviver, + const Symbol('encoding='): kPassthrough, + #done: kPassthrough.fuse(kFutureReviver), + }); + } + + @override + final String identifier; + + @override + List> get manifest => _fileSystem.manifest; +} diff --git a/lib/src/backends/record_replay/replay_link.dart b/lib/src/backends/record_replay/replay_link.dart index d23e826afd14c..6094ca5dded4e 100644 --- a/lib/src/backends/record_replay/replay_link.dart +++ b/lib/src/backends/record_replay/replay_link.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; +import 'codecs.dart'; import 'replay_file_system.dart'; import 'replay_file_system_entity.dart'; @@ -15,14 +16,20 @@ class ReplayLink extends ReplayFileSystemEntity implements Link { /// Creates a new `ReplayLink`. ReplayLink(ReplayFileSystemImpl fileSystem, String identifier) : super(fileSystem, identifier) { - // TODO(tvolkert): fill in resurrectors methods.addAll(>{ - #create: null, - #createSync: null, - #update: null, - #updateSync: null, - #target: null, - #targetSync: null, + #rename: linkReviver(fileSystem).fuse(kFutureReviver), + #renameSync: linkReviver(fileSystem), + #delete: linkReviver(fileSystem).fuse(kFutureReviver), + #create: linkReviver(fileSystem).fuse(kFutureReviver), + #createSync: kPassthrough, + #update: linkReviver(fileSystem).fuse(kFutureReviver), + #updateSync: kPassthrough, + #target: kPassthrough.fuse(kFutureReviver), + #targetSync: kPassthrough, + }); + + properties.addAll(>{ + #absolute: linkReviver(fileSystem), }); } } diff --git a/lib/src/backends/record_replay/replay_random_access_file.dart b/lib/src/backends/record_replay/replay_random_access_file.dart new file mode 100644 index 0000000000000..41774c4514219 --- /dev/null +++ b/lib/src/backends/record_replay/replay_random_access_file.dart @@ -0,0 +1,66 @@ +// 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:convert'; + +import 'package:file/file.dart'; + +import 'codecs.dart'; +import 'replay_file_system.dart'; +import 'replay_proxy_mixin.dart'; + +/// [RandomAccessFile] implementation that replays all invocation activity from a prior +/// recording. +class ReplayRandomAccessFile extends Object + with ReplayProxyMixin + implements RandomAccessFile { + final ReplayFileSystemImpl _fileSystem; + + /// Creates a new `ReplayIOSink`. + ReplayRandomAccessFile(this._fileSystem, this.identifier) { + Converter convertFutureThis = + randomAccessFileReviver(_fileSystem).fuse(kFutureReviver); + + methods.addAll(>{ + #close: convertFutureThis, + #closeSync: kPassthrough, + #readByte: kFutureReviver, + #readByteSync: kPassthrough, + #read: kFutureReviver, + #readSync: kPassthrough, + #readInto: kFutureReviver, + #readIntoSync: kPassthrough, + #writeByte: convertFutureThis, + #writeByteSync: kPassthrough, + #writeFrom: convertFutureThis, + #writeFromSync: kPassthrough, + #writeString: convertFutureThis, + #writeStringSync: kPassthrough, + #position: kFutureReviver, + #positionSync: kPassthrough, + #setPosition: convertFutureThis, + #setPositionSync: kPassthrough, + #truncate: convertFutureThis, + #truncateSync: kPassthrough, + #length: kFutureReviver, + #lengthSync: kPassthrough, + #flush: convertFutureThis, + #flushSync: kPassthrough, + #lock: convertFutureThis, + #lockSync: kPassthrough, + #unlock: convertFutureThis, + #unlockSync: kPassthrough, + }); + + properties.addAll(>{ + #path: kPassthrough, + }); + } + + @override + final String identifier; + + @override + List> get manifest => _fileSystem.manifest; +} diff --git a/test/replay_test.dart b/test/replay_test.dart index 65d317b8ac58a..d647f95648ac8 100644 --- a/test/replay_test.dart +++ b/test/replay_test.dart @@ -42,11 +42,16 @@ void main() { // ReplayFileSystem does not yet replay exceptions '.*(disallows|throws).*', - // TODO(tvolkert): Enable when ReplayFileSystem is complete. - 'FileSystem', - 'Directory', - 'File', - 'Link', + // TODO(tvolkert): re-enable when these are implemented + 'File > copy', + 'File > openRead', + 'File > openWrite', + 'File > readAsLines', + 'File > readAsString', + 'File > writeAsBytes', + 'File > writeAsString', + + 'File > open', // Not yet implemented in MemoryFileSystem ], );