Finish implementation of ReplayFileSystem (#33)

Fixes #11
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6114cbd..0a006da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-#### 1.0.2
+#### 2.0.0
 
 * Improved `toString` implementations in file system entity classes
 * Added `ForwardingFileSystem` and associated forwarding classes to the
diff --git a/lib/src/backends/record_replay/codecs.dart b/lib/src/backends/record_replay/codecs.dart
index 8b6bb55..2ecd1f7 100644
--- a/lib/src/backends/record_replay/codecs.dart
+++ b/lib/src/backends/record_replay/codecs.dart
@@ -20,75 +20,6 @@
 import 'replay_random_access_file.dart';
 import 'result_reference.dart';
 
-/// Converter that leaves object untouched.
-const Converter<dynamic, dynamic> kPassthrough = const _PassthroughConverter();
-
-/// Converter that will turn an object into a [Future] of that object.
-const Converter<dynamic, dynamic> kFutureReviver =
-    const _FutureReviver<dynamic>();
-
-/// Converter that will convert an [Iterable] into a [Stream].
-Converter<dynamic, dynamic> kStreamReviver = const _StreamReviver();
-
-/// Converter that will deserialize a [DateTime].
-const Converter<dynamic, dynamic> kDateTimeReviver = _DateTimeCodec.kDecoder;
-
-/// Converter that will deserialize a [FileStat].
-const Converter<dynamic, dynamic> kFileStatReviver = _FileStatCodec.kDecoder;
-
-/// Converter that will deserialize a [FileSystemEntityType].
-const Converter<dynamic, dynamic> kEntityTypeReviver =
-    _EntityTypeCodec.kDecoder;
-
-/// Converter that will deserialize a [path.Context].
-const Converter<dynamic, dynamic> kPathContextReviver =
-    _PathContextCodec.kDecoder;
-
-/// Converter that will deserialize a [Uri].
-const Converter<dynamic, dynamic> kUriReviver = _UriCodec.kDecoder;
-
-/// Converter that will deserialize a [Encoding].
-const Converter<dynamic, dynamic> kEncodingReviver = _EncodingCodec.kDecoder;
-
-/// Converter that will deserialize a [FileSystemEvent].
-const Converter<dynamic, dynamic> kFileSystemEventReviver =
-    _FileSystemEventCodec.kDecoder;
-
-/// Converter that will deserialize each element of a [List] by delegating to
-/// the specified [elementReviver].
-Converter<dynamic, dynamic> listReviver(
-        Converter<dynamic, dynamic> elementReviver) =>
-    new _ListReviver(elementReviver);
-
-/// Converter that will deserialize a blob file reference into the file's bytes.
-Converter<dynamic, dynamic> blobReviver(ReplayFileSystemImpl fileSystem) =>
-    new _BlobReviver(fileSystem);
-
-/// Converter that will deserialize a [ReplayDirectory].
-Converter<dynamic, dynamic> directoryReviver(ReplayFileSystemImpl fileSystem) =>
-    new _DirectoryReviver(fileSystem);
-
-/// Converter that will deserialize a [ReplayFile].
-Converter<dynamic, dynamic> fileReviver(ReplayFileSystemImpl fileSystem) =>
-    new _FileReviver(fileSystem);
-
-/// Converter that will deserialize a [ReplayLink].
-Converter<dynamic, dynamic> linkReviver(ReplayFileSystemImpl fileSystem) =>
-    new _LinkReviver(fileSystem);
-
-/// Converter that will deserialize an arbitrary [FileSystemEntity].
-Converter<dynamic, dynamic> entityReviver(ReplayFileSystemImpl fileSystem) =>
-    new _FileSystemEntityReviver(fileSystem);
-
-/// Converter that will deserialize a [ReplayRandomAccessFile].
-Converter<dynamic, dynamic> randomAccessFileReviver(
-        ReplayFileSystemImpl fileSystem) =>
-    new _RandomAccessFileReviver(fileSystem);
-
-/// Converter that will deserialize a [ReplayRandomAccessFile].
-Converter<dynamic, dynamic> ioSinkReviver(ReplayFileSystemImpl fileSystem) =>
-    new _IOSinkReviver(fileSystem);
-
 /// Encodes an arbitrary [object] into a JSON-ready representation (a number,
 /// boolean, string, null, list, or map).
 ///
@@ -118,24 +49,24 @@
   /// object, the first one will win.
   static const Map<TypeMatcher<dynamic>, Converter<Object, Object>> _encoders =
       const <TypeMatcher<dynamic>, Converter<Object, Object>>{
-    const TypeMatcher<num>(): const _PassthroughConverter(),
-    const TypeMatcher<bool>(): const _PassthroughConverter(),
-    const TypeMatcher<String>(): const _PassthroughConverter(),
-    const TypeMatcher<Null>(): const _PassthroughConverter(),
+    const TypeMatcher<num>(): const Passthrough<num>(),
+    const TypeMatcher<bool>(): const Passthrough<bool>(),
+    const TypeMatcher<String>(): const Passthrough<String>(),
+    const TypeMatcher<Null>(): const Passthrough<Null>(),
     const TypeMatcher<Iterable<dynamic>>(): const _IterableEncoder(),
     const TypeMatcher<Map<dynamic, dynamic>>(): const _MapEncoder(),
     const TypeMatcher<Symbol>(): const _SymbolEncoder(),
-    const TypeMatcher<DateTime>(): _DateTimeCodec.kEncoder,
-    const TypeMatcher<Uri>(): _UriCodec.kEncoder,
-    const TypeMatcher<path.Context>(): _PathContextCodec.kEncoder,
+    const TypeMatcher<DateTime>(): DateTimeCodec.serialize,
+    const TypeMatcher<Uri>(): UriCodec.serialize,
+    const TypeMatcher<path.Context>(): PathContextCodec.serialize,
     const TypeMatcher<ResultReference<dynamic>>(): const _ResultEncoder(),
     const TypeMatcher<LiveInvocationEvent<dynamic>>(): const _EventEncoder(),
     const TypeMatcher<ReplayAware>(): const _ReplayAwareEncoder(),
-    const TypeMatcher<Encoding>(): _EncodingCodec.kEncoder,
+    const TypeMatcher<Encoding>(): EncodingCodec.serialize,
     const TypeMatcher<FileMode>(): const _FileModeEncoder(),
-    const TypeMatcher<FileStat>(): _FileStatCodec.kEncoder,
-    const TypeMatcher<FileSystemEntityType>(): _EntityTypeCodec.kEncoder,
-    const TypeMatcher<FileSystemEvent>(): _FileSystemEventCodec.kEncoder,
+    const TypeMatcher<FileStat>(): FileStatCodec.serialize,
+    const TypeMatcher<FileSystemEntityType>(): EntityTypeCodec.serialize,
+    const TypeMatcher<FileSystemEvent>(): FileSystemEventCodec.serialize,
   };
 
   /// Default encoder (used for types not covered in [_encoders]).
@@ -155,11 +86,13 @@
   }
 }
 
-class _PassthroughConverter extends Converter<dynamic, dynamic> {
-  const _PassthroughConverter();
+/// Converter that leaves an object untouched.
+class Passthrough<T> extends Converter<T, T> {
+  /// Creates a new [Passthrough].
+  const Passthrough();
 
   @override
-  dynamic convert(dynamic input) => input;
+  T convert(T input) => input;
 }
 
 class _IterableEncoder extends Converter<Iterable<dynamic>, List<dynamic>> {
@@ -199,8 +132,10 @@
   String convert(Symbol input) => getSymbolName(input);
 }
 
-class _DateTimeCodec extends Codec<DateTime, int> {
-  const _DateTimeCodec();
+/// A [DateTimeCodec] serializes and deserializes [DateTime] instances.
+class DateTimeCodec extends Codec<DateTime, int> {
+  /// Creates a new [DateTimeCodec].
+  const DateTimeCodec();
 
   static int _encode(DateTime input) => input?.millisecondsSinceEpoch;
 
@@ -210,41 +145,49 @@
         : new DateTime.fromMillisecondsSinceEpoch(input);
   }
 
-  static const Converter<DateTime, int> kEncoder =
+  /// Converter that serializes [DateTime] instances.
+  static const Converter<DateTime, int> serialize =
       const _ForwardingConverter<DateTime, int>(_encode);
 
-  static const Converter<int, DateTime> kDecoder =
+  /// Converter that deserializes [DateTime] instances.
+  static const Converter<int, DateTime> deserialize =
       const _ForwardingConverter<int, DateTime>(_decode);
 
   @override
-  Converter<DateTime, int> get encoder => kEncoder;
+  Converter<DateTime, int> get encoder => serialize;
 
   @override
-  Converter<int, DateTime> get decoder => kDecoder;
+  Converter<int, DateTime> get decoder => deserialize;
 }
 
-class _UriCodec extends Codec<Uri, String> {
-  const _UriCodec();
+/// A [UriCodec] serializes and deserializes [Uri] instances.
+class UriCodec extends Codec<Uri, String> {
+  /// Creates a new [UriCodec].
+  const UriCodec();
 
   static String _encode(Uri input) => input.toString();
 
   static Uri _decode(String input) => Uri.parse(input);
 
-  static const Converter<Uri, String> kEncoder =
+  /// Converter that serializes [Uri] instances.
+  static const Converter<Uri, String> serialize =
       const _ForwardingConverter<Uri, String>(_encode);
 
-  static const Converter<String, Uri> kDecoder =
+  /// Converter that deserializes [Uri] instances.
+  static const Converter<String, Uri> deserialize =
       const _ForwardingConverter<String, Uri>(_decode);
 
   @override
-  Converter<Uri, String> get encoder => kEncoder;
+  Converter<Uri, String> get encoder => serialize;
 
   @override
-  Converter<String, Uri> get decoder => kDecoder;
+  Converter<String, Uri> get decoder => deserialize;
 }
 
-class _PathContextCodec extends Codec<path.Context, Map<String, String>> {
-  const _PathContextCodec();
+/// A [PathContextCodec] serializes and deserializes [path.Context] instances.
+class PathContextCodec extends Codec<path.Context, Map<String, String>> {
+  /// Creates a new [PathContextCodec].
+  const PathContextCodec();
 
   static Map<String, String> _encode(path.Context input) {
     return <String, String>{
@@ -264,17 +207,19 @@
     );
   }
 
-  static const Converter<path.Context, Map<String, String>> kEncoder =
+  /// Converter that serializes [path.Context] instances.
+  static const Converter<path.Context, Map<String, String>> serialize =
       const _ForwardingConverter<path.Context, Map<String, String>>(_encode);
 
-  static const Converter<Map<String, String>, path.Context> kDecoder =
+  /// Converter that deserializes [path.Context] instances.
+  static const Converter<Map<String, String>, path.Context> deserialize =
       const _ForwardingConverter<Map<String, String>, path.Context>(_decode);
 
   @override
-  Converter<path.Context, Map<String, String>> get encoder => kEncoder;
+  Converter<path.Context, Map<String, String>> get encoder => serialize;
 
   @override
-  Converter<Map<String, String>, path.Context> get decoder => kDecoder;
+  Converter<Map<String, String>, path.Context> get decoder => deserialize;
 }
 
 class _ResultEncoder extends Converter<ResultReference<dynamic>, Object> {
@@ -301,8 +246,10 @@
   String convert(ReplayAware input) => input.identifier;
 }
 
-class _EncodingCodec extends Codec<Encoding, String> {
-  const _EncodingCodec();
+/// An [EncodingCodec] serializes and deserializes [Encoding] instances.
+class EncodingCodec extends Codec<Encoding, String> {
+  /// Creates a new [EncodingCodec].
+  const EncodingCodec();
 
   static String _encode(Encoding input) => input.name;
 
@@ -315,17 +262,19 @@
     return null;
   }
 
-  static const Converter<Encoding, String> kEncoder =
+  /// Converter that serializes [Encoding] instances.
+  static const Converter<Encoding, String> serialize =
       const _ForwardingConverter<Encoding, String>(_encode);
 
-  static const Converter<String, Encoding> kDecoder =
+  /// Converter that deserializes [Encoding] instances.
+  static const Converter<String, Encoding> deserialize =
       const _ForwardingConverter<String, Encoding>(_decode);
 
   @override
-  Converter<Encoding, String> get encoder => kEncoder;
+  Converter<Encoding, String> get encoder => serialize;
 
   @override
-  Converter<String, Encoding> get decoder => kDecoder;
+  Converter<String, Encoding> get decoder => deserialize;
 }
 
 class _FileModeEncoder extends Converter<FileMode, String> {
@@ -349,15 +298,17 @@
   }
 }
 
-class _FileStatCodec extends Codec<FileStat, Map<String, Object>> {
-  const _FileStatCodec();
+/// An [FileStatCodec] serializes and deserializes [FileStat] instances.
+class FileStatCodec extends Codec<FileStat, Map<String, Object>> {
+  /// Creates a new [FileStatCodec].
+  const FileStatCodec();
 
   static Map<String, Object> _encode(FileStat input) {
     return <String, dynamic>{
-      'changed': const _DateTimeCodec().encode(input.changed),
-      'modified': const _DateTimeCodec().encode(input.modified),
-      'accessed': const _DateTimeCodec().encode(input.accessed),
-      'type': const _EntityTypeCodec().encode(input.type),
+      'changed': const DateTimeCodec().encode(input.changed),
+      'modified': const DateTimeCodec().encode(input.modified),
+      'accessed': const DateTimeCodec().encode(input.accessed),
+      'type': const EntityTypeCodec().encode(input.type),
       'mode': input.mode,
       'size': input.size,
       'modeString': input.modeString(),
@@ -367,21 +318,26 @@
   static FileStat _decode(Map<String, Object> input) =>
       new ReplayFileStat(input);
 
-  static const Converter<FileStat, Map<String, Object>> kEncoder =
+  /// Converter that serializes [FileStat] instances.
+  static const Converter<FileStat, Map<String, Object>> serialize =
       const _ForwardingConverter<FileStat, Map<String, Object>>(_encode);
 
-  static const Converter<Map<String, Object>, FileStat> kDecoder =
+  /// Converter that deserializes [FileStat] instances.
+  static const Converter<Map<String, Object>, FileStat> deserialize =
       const _ForwardingConverter<Map<String, Object>, FileStat>(_decode);
 
   @override
-  Converter<FileStat, Map<String, Object>> get encoder => kEncoder;
+  Converter<FileStat, Map<String, Object>> get encoder => serialize;
 
   @override
-  Converter<Map<String, Object>, FileStat> get decoder => kDecoder;
+  Converter<Map<String, Object>, FileStat> get decoder => deserialize;
 }
 
-class _EntityTypeCodec extends Codec<FileSystemEntityType, String> {
-  const _EntityTypeCodec();
+/// An [EntityTypeCodec] serializes and deserializes [FileSystemEntity]
+/// instances.
+class EntityTypeCodec extends Codec<FileSystemEntityType, String> {
+  /// Creates a new [EntityTypeCodec].
+  const EntityTypeCodec();
 
   static String _encode(FileSystemEntityType input) => input.toString();
 
@@ -394,22 +350,26 @@
     }[input];
   }
 
-  static const Converter<FileSystemEntityType, String> kEncoder =
+  /// Converter that serializes [FileSystemEntityType] instances.
+  static const Converter<FileSystemEntityType, String> serialize =
       const _ForwardingConverter<FileSystemEntityType, String>(_encode);
 
-  static const Converter<String, FileSystemEntityType> kDecoder =
+  /// Converter that deserializes [FileSystemEntityType] instances.
+  static const Converter<String, FileSystemEntityType> deserialize =
       const _ForwardingConverter<String, FileSystemEntityType>(_decode);
 
   @override
-  Converter<FileSystemEntityType, String> get encoder => kEncoder;
+  Converter<FileSystemEntityType, String> get encoder => serialize;
 
   @override
-  Converter<String, FileSystemEntityType> get decoder => kDecoder;
+  Converter<String, FileSystemEntityType> get decoder => deserialize;
 }
 
-class _FileSystemEventCodec
-    extends Codec<FileSystemEvent, Map<String, Object>> {
-  const _FileSystemEventCodec();
+/// A [FileSystemEventCodec] serializes and deserializes [FileSystemEvent]
+/// instances.
+class FileSystemEventCodec extends Codec<FileSystemEvent, Map<String, Object>> {
+  /// Creates a new [FileSystemEventCodec].
+  const FileSystemEventCodec();
 
   static Map<String, Object> _encode(FileSystemEvent input) {
     return <String, Object>{
@@ -422,17 +382,19 @@
   static FileSystemEvent _decode(Map<String, Object> input) =>
       new _FileSystemEvent(input);
 
-  static const Converter<FileSystemEvent, Map<String, Object>> kEncoder =
+  /// Converter that serializes [FileSystemEvent] instances.
+  static const Converter<FileSystemEvent, Map<String, Object>> serialize =
       const _ForwardingConverter<FileSystemEvent, Map<String, Object>>(_encode);
 
-  static const Converter<Map<String, Object>, FileSystemEvent> kDecoder =
+  /// Converter that deserializes [FileSystemEvent] instances.
+  static const Converter<Map<String, Object>, FileSystemEvent> deserialize =
       const _ForwardingConverter<Map<String, Object>, FileSystemEvent>(_decode);
 
   @override
-  Converter<FileSystemEvent, Map<String, Object>> get encoder => kEncoder;
+  Converter<FileSystemEvent, Map<String, Object>> get encoder => serialize;
 
   @override
-  Converter<Map<String, Object>, FileSystemEvent> get decoder => kDecoder;
+  Converter<Map<String, Object>, FileSystemEvent> get decoder => deserialize;
 }
 
 class _FileSystemEvent implements FileSystemEvent {
@@ -450,100 +412,143 @@
   bool get isDirectory => _data['isDirectory'];
 }
 
-class _FutureReviver<T> extends Converter<T, Future<T>> {
-  const _FutureReviver();
+/// Converts an object into a [Future] that completes with that object.
+class ToFuture<T> extends Converter<T, Future<T>> {
+  /// Creates a new [ToFuture].
+  const ToFuture();
 
   @override
   Future<T> convert(T input) async => input;
 }
 
-class _DirectoryReviver extends Converter<String, Directory> {
-  final ReplayFileSystemImpl fileSystem;
-  const _DirectoryReviver(this.fileSystem);
+/// Converts an object into a single-element [List] containing that object.
+class Listify<T> extends Converter<T, List<T>> {
+  /// Creates a new [Listify].
+  const Listify();
 
   @override
-  Directory convert(String input) => new ReplayDirectory(fileSystem, input);
+  List<T> convert(T input) => <T>[input];
 }
 
-class _FileReviver extends Converter<String, File> {
-  final ReplayFileSystemImpl fileSystem;
-  const _FileReviver(this.fileSystem);
+/// Revives a [Directory] entity reference into a [ReplayDirectory].
+class ReviveDirectory extends Converter<String, Directory> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveDirectory].
+  const ReviveDirectory(this._fileSystem);
 
   @override
-  File convert(String input) => new ReplayFile(fileSystem, input);
+  Directory convert(String input) => new ReplayDirectory(_fileSystem, input);
 }
 
-class _LinkReviver extends Converter<String, Link> {
-  final ReplayFileSystemImpl fileSystem;
-  const _LinkReviver(this.fileSystem);
+/// Revives a [File] entity reference into a [ReplayFile].
+class ReviveFile extends Converter<String, File> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveFile].
+  const ReviveFile(this._fileSystem);
 
   @override
-  Link convert(String input) => new ReplayLink(fileSystem, input);
+  File convert(String input) => new ReplayFile(_fileSystem, input);
 }
 
-class _FileSystemEntityReviver extends Converter<String, FileSystemEntity> {
-  final ReplayFileSystemImpl fileSystem;
-  const _FileSystemEntityReviver(this.fileSystem);
+/// Revives a [Link] entity reference into a [ReplayLink].
+class ReviveLink extends Converter<String, Link> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveLink].
+  const ReviveLink(this._fileSystem);
+
+  @override
+  Link convert(String input) => new ReplayLink(_fileSystem, input);
+}
+
+/// Revives a [FileSystemEntity] entity reference into a [ReplayDirectory],
+/// [ReplayFile], or a [ReplayLink] depending on the identifier of the entity
+/// reference.
+class ReviveFileSystemEntity extends Converter<String, FileSystemEntity> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveFileSystemEntity].
+  const ReviveFileSystemEntity(this._fileSystem);
 
   @override
   FileSystemEntity convert(String input) {
     if (input.contains('Directory')) {
-      return new ReplayDirectory(fileSystem, input);
+      return new ReplayDirectory(_fileSystem, input);
     } else if (input.contains('File')) {
-      return new ReplayFile(fileSystem, input);
+      return new ReplayFile(_fileSystem, input);
     } else {
-      return new ReplayLink(fileSystem, input);
+      return new ReplayLink(_fileSystem, input);
     }
   }
 }
 
-class _RandomAccessFileReviver extends Converter<String, RandomAccessFile> {
-  final ReplayFileSystemImpl fileSystem;
-  const _RandomAccessFileReviver(this.fileSystem);
+/// Revives a [RandomAccessFile] entity reference into a
+/// [ReplayRandomAccessFile].
+class ReviveRandomAccessFile extends Converter<String, RandomAccessFile> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveRandomAccessFile] that will derive its behavior
+  /// from the specified file system's recording.
+  const ReviveRandomAccessFile(this._fileSystem);
 
   @override
   RandomAccessFile convert(String input) =>
-      new ReplayRandomAccessFile(fileSystem, input);
+      new ReplayRandomAccessFile(_fileSystem, input);
 }
 
-class _IOSinkReviver extends Converter<String, IOSink> {
-  final ReplayFileSystemImpl fileSystem;
-  const _IOSinkReviver(this.fileSystem);
+/// Revives an [IOSink] entity reference into a [ReplayIOSink].
+class ReviveIOSink extends Converter<String, IOSink> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [ReviveIOSink] that will derive its behavior from the
+  /// specified file system's recording.
+  const ReviveIOSink(this._fileSystem);
 
   @override
-  IOSink convert(String input) => new ReplayIOSink(fileSystem, input);
+  IOSink convert(String input) => new ReplayIOSink(_fileSystem, input);
 }
 
-class _ListReviver extends Converter<Iterable<dynamic>, List<dynamic>> {
-  final Converter<dynamic, dynamic> elementReviver;
+/// Converts all elements of a [List], returning a new [List] of converted
+/// elements.
+class ConvertElements<S, T> extends Converter<List<S>, List<T>> {
+  final Converter<S, T> _delegate;
 
-  const _ListReviver(this.elementReviver);
+  /// Creates a new [ConvertElements] that will use the specified
+  /// [elementConverter] to convert the elements of an [Iterable].
+  const ConvertElements(Converter<S, T> elementConverter)
+      : _delegate = elementConverter;
 
   @override
-  List<dynamic> convert(Iterable<dynamic> input) =>
-      input.map(elementReviver.convert).toList();
+  List<T> convert(List<S> input) => input.map(_delegate.convert).toList();
 }
 
-class _StreamReviver extends Converter<List<dynamic>, Stream<dynamic>> {
-  const _StreamReviver();
+/// Converts a [List] of elements into a [Stream] of the same elements.
+class ToStream<T> extends Converter<List<T>, Stream<T>> {
+  /// Creates a new [ToStream].
+  const ToStream();
 
   @override
-  Stream<dynamic> convert(List<dynamic> input) {
-    return new Stream<dynamic>.fromIterable(input);
-  }
+  Stream<T> convert(List<T> input) => new Stream<T>.fromIterable(input);
 }
 
-class _BlobReviver extends Converter<String, List<int>> {
-  final ReplayFileSystemImpl fileSystem;
-  const _BlobReviver(this.fileSystem);
+/// Converts a blob reference (serialized as a [String] of the form
+/// `!<filename>`) into a byte list.
+class BlobToBytes extends Converter<String, List<int>> {
+  final ReplayFileSystemImpl _fileSystem;
+
+  /// Creates a new [BlobToBytes] that will use the specified file system's
+  /// recording to load the blob.
+  const BlobToBytes(this._fileSystem);
 
   @override
   List<int> 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);
+    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/replay_directory.dart b/lib/src/backends/record_replay/replay_directory.dart
index 75e3b18..2d264e5 100644
--- a/lib/src/backends/record_replay/replay_directory.dart
+++ b/lib/src/backends/record_replay/replay_directory.dart
@@ -2,6 +2,7 @@
 // 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 'package:file/file.dart';
@@ -16,24 +17,29 @@
   /// Creates a new `ReplayDirectory`.
   ReplayDirectory(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
-    Converter<dynamic, dynamic> convertThis = directoryReviver(fileSystem);
-    Converter<dynamic, dynamic> convertFutureThis =
-        convertThis.fuse(kFutureReviver);
+    Converter<String, Directory> reviveDirectory =
+        new ReviveDirectory(fileSystem);
+    Converter<String, Future<Directory>> reviveFutureDirectory =
+        reviveDirectory.fuse(const ToFuture<Directory>());
+    Converter<String, FileSystemEntity> reviveEntity =
+        new ReviveFileSystemEntity(fileSystem);
+    Converter<List<String>, List<FileSystemEntity>> reviveEntities =
+        new ConvertElements<String, FileSystemEntity>(reviveEntity);
 
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #rename: convertFutureThis,
-      #renameSync: convertThis,
-      #delete: convertFutureThis,
-      #create: convertFutureThis,
-      #createSync: kPassthrough,
-      #createTemp: convertFutureThis,
-      #createTempSync: convertThis,
-      #list: listReviver(entityReviver(fileSystem)).fuse(kStreamReviver),
-      #listSync: listReviver(entityReviver(fileSystem)),
+      #rename: reviveFutureDirectory,
+      #renameSync: reviveDirectory,
+      #delete: reviveFutureDirectory,
+      #create: reviveFutureDirectory,
+      #createSync: const Passthrough<Null>(),
+      #createTemp: reviveFutureDirectory,
+      #createTempSync: reviveDirectory,
+      #list: reviveEntities.fuse(const ToStream<FileSystemEntity>()),
+      #listSync: reviveEntities,
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #absolute: convertThis,
+      #absolute: reviveDirectory,
     });
   }
 }
diff --git a/lib/src/backends/record_replay/replay_file.dart b/lib/src/backends/record_replay/replay_file.dart
index 149162d..fa5faf8 100644
--- a/lib/src/backends/record_replay/replay_file.dart
+++ b/lib/src/backends/record_replay/replay_file.dart
@@ -2,6 +2,7 @@
 // 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 'package:file/file.dart';
@@ -16,40 +17,52 @@
   /// Creates a new `ReplayFile`.
   ReplayFile(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
-    Converter<dynamic, dynamic> convertThis = fileReviver(fileSystem);
-    Converter<dynamic, dynamic> convertFutureThis =
-        convertThis.fuse(kFutureReviver);
+    Converter<String, File> reviveFile = new ReviveFile(fileSystem);
+    Converter<String, Future<File>> reviveFileAsFuture =
+        reviveFile.fuse(const ToFuture<File>());
+    Converter<String, List<int>> blobToBytes = new BlobToBytes(fileSystem);
+    Converter<String, String> blobToString = blobToBytes.fuse(UTF8.decoder);
+    Converter<String, RandomAccessFile> reviveRandomAccessFile =
+        new ReviveRandomAccessFile(fileSystem);
+    // TODO(tvolkert) remove `as`: https://github.com/dart-lang/sdk/issues/28748
+    Converter<String, List<String>> lineSplitter =
+        const LineSplitter() as Converter<String, List<String>>;
+    Converter<String, List<String>> blobToLines =
+        blobToString.fuse(lineSplitter);
+    Converter<String, Stream<List<int>>> blobToByteStream = blobToBytes
+        .fuse(const Listify<List<int>>())
+        .fuse(const ToStream<List<int>>());
 
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #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,
+      #rename: reviveFileAsFuture,
+      #renameSync: reviveFile,
+      #delete: reviveFileAsFuture,
+      #create: reviveFileAsFuture,
+      #createSync: const Passthrough<Null>(),
+      #copy: reviveFileAsFuture,
+      #copySync: reviveFile,
+      #length: const ToFuture<int>(),
+      #lengthSync: const Passthrough<int>(),
+      #lastModified: DateTimeCodec.deserialize.fuse(const ToFuture<DateTime>()),
+      #lastModifiedSync: DateTimeCodec.deserialize,
+      #open: reviveRandomAccessFile.fuse(const ToFuture<RandomAccessFile>()),
+      #openSync: reviveRandomAccessFile,
+      #openRead: blobToByteStream,
+      #openWrite: new ReviveIOSink(fileSystem),
+      #readAsBytes: blobToBytes.fuse(const ToFuture<List<int>>()),
+      #readAsBytesSync: blobToBytes,
+      #readAsString: blobToString.fuse(const ToFuture<String>()),
+      #readAsStringSync: blobToString,
+      #readAsLines: blobToLines.fuse(const ToFuture<List<String>>()),
+      #readAsLinesSync: blobToLines,
+      #writeAsBytes: reviveFileAsFuture,
+      #writeAsBytesSync: const Passthrough<Null>(),
+      #writeAsString: reviveFileAsFuture,
+      #writeAsStringSync: const Passthrough<Null>(),
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #absolute: convertThis,
+      #absolute: reviveFile,
     });
   }
 }
diff --git a/lib/src/backends/record_replay/replay_file_stat.dart b/lib/src/backends/record_replay/replay_file_stat.dart
index 2e91c75..5883963 100644
--- a/lib/src/backends/record_replay/replay_file_stat.dart
+++ b/lib/src/backends/record_replay/replay_file_stat.dart
@@ -16,16 +16,17 @@
   ReplayFileStat(Map<String, dynamic> data) : _data = data;
 
   @override
-  DateTime get changed => kDateTimeReviver.convert(_data['changed']);
+  DateTime get changed => DateTimeCodec.deserialize.convert(_data['changed']);
 
   @override
-  DateTime get modified => kDateTimeReviver.convert(_data['modified']);
+  DateTime get modified => DateTimeCodec.deserialize.convert(_data['modified']);
 
   @override
-  DateTime get accessed => kDateTimeReviver.convert(_data['accessed']);
+  DateTime get accessed => DateTimeCodec.deserialize.convert(_data['accessed']);
 
   @override
-  FileSystemEntityType get type => kEntityTypeReviver.convert(_data['type']);
+  FileSystemEntityType get type =>
+      EntityTypeCodec.deserialize.convert(_data['type']);
 
   @override
   int get mode => _data['mode'];
diff --git a/lib/src/backends/record_replay/replay_file_system.dart b/lib/src/backends/record_replay/replay_file_system.dart
index 93efa5e..0361071 100644
--- a/lib/src/backends/record_replay/replay_file_system.dart
+++ b/lib/src/backends/record_replay/replay_file_system.dart
@@ -70,24 +70,28 @@
     implements ReplayFileSystem, ReplayAware {
   /// Creates a new `ReplayFileSystemImpl`.
   ReplayFileSystemImpl(this.recording, this.manifest) {
+    Converter<String, Directory> reviveDirectory = new ReviveDirectory(this);
+    ToFuture<FileSystemEntityType> toFutureType =
+        const ToFuture<FileSystemEntityType>();
+
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #directory: directoryReviver(this),
-      #file: fileReviver(this),
-      #link: linkReviver(this),
-      #stat: kFileStatReviver.fuse(kFutureReviver),
-      #statSync: kFileStatReviver,
-      #identical: kPassthrough.fuse(kFutureReviver),
-      #identicalSync: kPassthrough,
-      #type: kEntityTypeReviver.fuse(kFutureReviver),
-      #typeSync: kEntityTypeReviver,
+      #directory: reviveDirectory,
+      #file: new ReviveFile(this),
+      #link: new ReviveLink(this),
+      #stat: FileStatCodec.deserialize.fuse(const ToFuture<FileStat>()),
+      #statSync: FileStatCodec.deserialize,
+      #identical: const Passthrough<bool>().fuse(const ToFuture<bool>()),
+      #identicalSync: const Passthrough<bool>(),
+      #type: EntityTypeCodec.deserialize.fuse(toFutureType),
+      #typeSync: EntityTypeCodec.deserialize,
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #path: kPathContextReviver,
-      #systemTempDirectory: directoryReviver(this),
-      #currentDirectory: directoryReviver(this),
-      const Symbol('currentDirectory='): kPassthrough,
-      #isWatchSupported: kPassthrough,
+      #path: PathContextCodec.deserialize,
+      #systemTempDirectory: reviveDirectory,
+      #currentDirectory: reviveDirectory,
+      const Symbol('currentDirectory='): const Passthrough<Null>(),
+      #isWatchSupported: const Passthrough<bool>(),
     });
   }
 
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 da5631d..986d14b 100644
--- a/lib/src/backends/record_replay/replay_file_system_entity.dart
+++ b/lib/src/backends/record_replay/replay_file_system_entity.dart
@@ -2,6 +2,7 @@
 // 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 'package:file/file.dart';
@@ -17,24 +18,30 @@
     implements FileSystemEntity {
   /// Creates a new `ReplayFileSystemEntity`.
   ReplayFileSystemEntity(this.fileSystem, this.identifier) {
+    Converter<List<Map<String, Object>>, List<FileSystemEvent>> toEvents =
+        const ConvertElements<Map<String, Object>, FileSystemEvent>(
+            FileSystemEventCodec.deserialize);
+    Converter<List<Map<String, Object>>, Stream<FileSystemEvent>>
+        toEventStream = toEvents.fuse(const ToStream<FileSystemEvent>());
+
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #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),
+      #exists: const ToFuture<bool>(),
+      #existsSync: const Passthrough<bool>(),
+      #resolveSymbolicLinks: const ToFuture<String>(),
+      #resolveSymbolicLinksSync: const Passthrough<String>(),
+      #stat: FileStatCodec.deserialize.fuse(const ToFuture<FileStat>()),
+      #statSync: FileStatCodec.deserialize,
+      #deleteSync: const Passthrough<Null>(),
+      #watch: toEventStream,
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #path: kPassthrough,
-      #uri: kUriReviver,
-      #isAbsolute: kPassthrough,
-      #parent: directoryReviver(fileSystem),
-      #basename: kPassthrough,
-      #dirname: kPassthrough,
+      #path: const Passthrough<String>(),
+      #uri: UriCodec.deserialize,
+      #isAbsolute: const Passthrough<bool>(),
+      #parent: new ReviveDirectory(fileSystem),
+      #basename: const Passthrough<String>(),
+      #dirname: const Passthrough<String>(),
     });
   }
 
diff --git a/lib/src/backends/record_replay/replay_io_sink.dart b/lib/src/backends/record_replay/replay_io_sink.dart
index b625810..2bacb1f 100644
--- a/lib/src/backends/record_replay/replay_io_sink.dart
+++ b/lib/src/backends/record_replay/replay_io_sink.dart
@@ -15,24 +15,24 @@
 class ReplayIOSink extends Object with ReplayProxyMixin implements IOSink {
   final ReplayFileSystemImpl _fileSystem;
 
-  /// Creates a new `ReplayIOSink`.
+  /// Creates a new [ReplayIOSink].
   ReplayIOSink(this._fileSystem, this.identifier) {
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #add: kPassthrough,
-      #write: kPassthrough,
-      #writeAll: kPassthrough,
-      #writeln: kPassthrough,
-      #writeCharCode: kPassthrough,
-      #addError: kPassthrough,
-      #addStream: kFutureReviver,
-      #flush: kFutureReviver,
-      #close: kFutureReviver,
+      #add: const Passthrough<Null>(),
+      #write: const Passthrough<Null>(),
+      #writeAll: const Passthrough<Null>(),
+      #writeln: const Passthrough<Null>(),
+      #writeCharCode: const Passthrough<Null>(),
+      #addError: const Passthrough<Null>(),
+      #addStream: const ToFuture<dynamic>(),
+      #flush: const ToFuture<dynamic>(),
+      #close: const ToFuture<dynamic>(),
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #encoding: kEncodingReviver,
-      const Symbol('encoding='): kPassthrough,
-      #done: kPassthrough.fuse(kFutureReviver),
+      #encoding: EncodingCodec.deserialize,
+      const Symbol('encoding='): const Passthrough<Null>(),
+      #done: const Passthrough<dynamic>().fuse(const ToFuture<dynamic>()),
     });
   }
 
diff --git a/lib/src/backends/record_replay/replay_link.dart b/lib/src/backends/record_replay/replay_link.dart
index 6094ca5..bf29d01 100644
--- a/lib/src/backends/record_replay/replay_link.dart
+++ b/lib/src/backends/record_replay/replay_link.dart
@@ -2,6 +2,7 @@
 // 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 'package:file/file.dart';
@@ -16,20 +17,24 @@
   /// Creates a new `ReplayLink`.
   ReplayLink(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
+    Converter<String, Link> reviveLink = new ReviveLink(fileSystem);
+    Converter<String, Future<Link>> reviveLinkAsFuture =
+        reviveLink.fuse(const ToFuture<Link>());
+
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #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,
+      #rename: reviveLinkAsFuture,
+      #renameSync: reviveLink,
+      #delete: reviveLinkAsFuture,
+      #create: reviveLinkAsFuture,
+      #createSync: const Passthrough<Null>(),
+      #update: reviveLinkAsFuture,
+      #updateSync: const Passthrough<Null>(),
+      #target: const Passthrough<String>().fuse(const ToFuture<String>()),
+      #targetSync: const Passthrough<String>(),
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #absolute: linkReviver(fileSystem),
+      #absolute: reviveLink,
     });
   }
 }
diff --git a/lib/src/backends/record_replay/replay_random_access_file.dart b/lib/src/backends/record_replay/replay_random_access_file.dart
index 41774c4..c1219b4 100644
--- a/lib/src/backends/record_replay/replay_random_access_file.dart
+++ b/lib/src/backends/record_replay/replay_random_access_file.dart
@@ -2,6 +2,7 @@
 // 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 'package:file/file.dart';
@@ -17,44 +18,45 @@
     implements RandomAccessFile {
   final ReplayFileSystemImpl _fileSystem;
 
-  /// Creates a new `ReplayIOSink`.
+  /// Creates a new [ReplayRandomAccessFile].
   ReplayRandomAccessFile(this._fileSystem, this.identifier) {
-    Converter<dynamic, dynamic> convertFutureThis =
-        randomAccessFileReviver(_fileSystem).fuse(kFutureReviver);
+    ToFuture<RandomAccessFile> toFuture = const ToFuture<RandomAccessFile>();
+    Converter<String, Future<RandomAccessFile>> reviveRandomAccessFileAsFuture =
+        new ReviveRandomAccessFile(_fileSystem).fuse(toFuture);
 
     methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #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,
+      #close: reviveRandomAccessFileAsFuture,
+      #closeSync: const Passthrough<Null>(),
+      #readByte: const ToFuture<int>(),
+      #readByteSync: const Passthrough<int>(),
+      #read: const ToFuture<List<int>>(),
+      #readSync: const Passthrough<List<int>>(),
+      #readInto: const ToFuture<int>(),
+      #readIntoSync: const Passthrough<int>(),
+      #writeByte: reviveRandomAccessFileAsFuture,
+      #writeByteSync: const Passthrough<int>(),
+      #writeFrom: reviveRandomAccessFileAsFuture,
+      #writeFromSync: const Passthrough<Null>(),
+      #writeString: reviveRandomAccessFileAsFuture,
+      #writeStringSync: const Passthrough<Null>(),
+      #position: const ToFuture<int>(),
+      #positionSync: const Passthrough<int>(),
+      #setPosition: reviveRandomAccessFileAsFuture,
+      #setPositionSync: const Passthrough<Null>(),
+      #truncate: reviveRandomAccessFileAsFuture,
+      #truncateSync: const Passthrough<Null>(),
+      #length: const ToFuture<int>(),
+      #lengthSync: const Passthrough<int>(),
+      #flush: reviveRandomAccessFileAsFuture,
+      #flushSync: const Passthrough<Null>(),
+      #lock: reviveRandomAccessFileAsFuture,
+      #lockSync: const Passthrough<Null>(),
+      #unlock: reviveRandomAccessFileAsFuture,
+      #unlockSync: const Passthrough<Null>(),
     });
 
     properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
-      #path: kPassthrough,
+      #path: const Passthrough<String>(),
     });
   }
 
diff --git a/pubspec.yaml b/pubspec.yaml
index d9392a5..a58037f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,11 @@
 name: file
-version: 1.0.1
+version: 2.0.0
 authors:
 - Matan Lurey <matanl@google.com>
 - Yegor Jbanov <yjbanov@google.com>
 - Todd Volkert <tvolkert@google.com>
 description: A pluggable, mockable file system abstraction for Dart.
-homepage: https://github.com/matanlurey/file
+homepage: https://github.com/google/file.dart
 
 dependencies:
   intl: ^0.14.0
diff --git a/test/common_tests.dart b/test/common_tests.dart
index 21312e3..630af33 100644
--- a/test/common_tests.dart
+++ b/test/common_tests.dart
@@ -10,7 +10,7 @@
 import 'package:file/file.dart';
 import 'package:file/testing.dart';
 import 'package:test/test.dart';
-import 'package:test/test.dart' as testpkg show group, test, setUp;
+import 'package:test/test.dart' as testpkg show group, setUp, tearDown, test;
 
 /// Callback used in [runCommonTests] to produce the root folder in which all
 /// file system entities will be created.
@@ -21,8 +21,9 @@
 /// [FileSystem].
 typedef dynamic FileSystemGenerator();
 
-/// A function to run before tests (passed to [setUp]).
-typedef dynamic SetUpCallback();
+/// A function to run before tests (passed to [setUp]) or after tests
+/// (passed to [tearDown]).
+typedef dynamic SetUpTearDown();
 
 /// Runs a suite of tests common to all file system implementations. All file
 /// system implementations should run *at least* these tests to ensure
@@ -54,7 +55,8 @@
 
   group('common', () {
     FileSystemGenerator createFs;
-    List<SetUpCallback> setUps;
+    List<SetUpTearDown> setUps;
+    List<SetUpTearDown> tearDowns;
     FileSystem fs;
     String root;
 
@@ -72,7 +74,8 @@
 
     testpkg.setUp(() async {
       createFs = createFileSystem;
-      setUps = <SetUpCallback>[];
+      setUps = <SetUpTearDown>[];
+      tearDowns = <SetUpTearDown>[];
       fs = null;
       root = null;
     });
@@ -81,6 +84,14 @@
       testpkg.setUp(replay == null ? callback : () => setUps.add(callback));
     }
 
+    void tearDown(callback()) {
+      if (replay == null) {
+        testpkg.tearDown(callback);
+      } else {
+        testpkg.setUp(() => tearDowns.insert(0, callback));
+      }
+    }
+
     void group(String description, body()) =>
         skipIfNecessary(description, () => testpkg.group(description, body));
 
@@ -90,13 +101,22 @@
           } else {
             group('rerun', () {
               testpkg.setUp(() async {
-                await Future.forEach(setUps, (SetUpCallback setUp) => setUp());
+                await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
                 await body();
+                for (SetUpTearDown tearDown in tearDowns) {
+                  await tearDown();
+                }
                 createFs = replay;
-                await Future.forEach(setUps, (SetUpCallback setUp) => setUp());
+                await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
               });
 
               testpkg.test(description, body);
+
+              testpkg.tearDown(() async {
+                for (SetUpTearDown tearDown in tearDowns) {
+                  await tearDown();
+                }
+              });
             });
           }
         });
diff --git a/test/replay_test.dart b/test/replay_test.dart
index d647f95..f68ebf3 100644
--- a/test/replay_test.dart
+++ b/test/replay_test.dart
@@ -42,14 +42,8 @@
         // ReplayFileSystem does not yet replay exceptions
         '.*(disallows|throws).*',
 
-        // TODO(tvolkert): re-enable when these are implemented
-        'File > copy',
-        'File > openRead',
-        'File > openWrite',
-        'File > readAsLines',
-        'File > readAsString',
-        'File > writeAsBytes',
-        'File > writeAsString',
+        // TODO(tvolkert): Fix breakage, and re-enable
+        'File > openWrite > ioSink > addStream',
 
         'File > open', // Not yet implemented in MemoryFileSystem
       ],