blob: 5a36354f5ab91df576ddbffabc89b66c0c040d80 [file] [log] [blame]
// 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 'package:meta/meta.dart';
import 'common.dart';
import 'errors.dart';
import 'recording_file_system.dart';
import 'replay_proxy_mixin.dart';
import 'resurrectors.dart';
/// A file system that replays invocations from a prior recording for use
/// in tests.
///
/// This will replay all invocations (methods, property getters, and property
/// setters) that occur on it, based on an opaque recording that was generated
/// in [RecordingFileSystem]. All activity in the [File], [Directory], [Link],
/// [IOSink], and [RandomAccessFile] instances returned from this API will also
/// be replayed from the same recording.
///
/// Once an invocation has been replayed once, it is marked as such and will
/// not be eligible for further replay. If an eligible invocation cannot be
/// found that matches an incoming invocation, a [NoMatchingInvocationError]
/// will be thrown.
///
/// This class is intended for use in tests, where you would otherwise have to
/// set up complex mocks or fake file systems. With this class, the process is
/// as follows:
///
/// - You record the file system activity during a real run of your program
/// by injecting a `RecordingFileSystem` that delegates to your real file
/// system.
/// - You serialize that recording to disk as your program finishes.
/// - You use that recording in tests to create a mock file system that knows
/// how to respond to the exact invocations your program makes. Any
/// invocations that aren't in the recording will throw, and you can make
/// assertions in your tests about which methods were invoked and in what
/// order.
///
/// See also:
/// - [RecordingFileSystem]
abstract class ReplayFileSystem extends FileSystem {
/// Creates a new `ReplayFileSystem`.
///
/// Recording data will be loaded from the specified [recording] location.
/// This location must have been created by [RecordingFileSystem], or an
/// [ArgumentError] will be thrown.
factory ReplayFileSystem({
@required Directory recording,
}) {
String dirname = recording.path;
String path = recording.fileSystem.path.join(dirname, kManifestName);
File manifestFile = recording.fileSystem.file(path);
if (!manifestFile.existsSync()) {
throw new ArgumentError('Not a valid recording directory: $dirname');
}
List<Map<String, dynamic>> manifest =
new JsonDecoder().convert(manifestFile.readAsStringSync());
return new ReplayFileSystemImpl(manifest);
}
}
/// Non-exported implementation class for `ReplayFileSystem`.
class ReplayFileSystemImpl extends FileSystem
with ReplayProxyMixin
implements ReplayFileSystem, ReplayAware {
/// Creates a new `ReplayFileSystemImpl`.
ReplayFileSystemImpl(this.manifest) {
methods.addAll(<Symbol, Resurrector>{
#directory: resurrectDirectory(this),
#file: resurrectFile(this),
#link: resurrectLink(this),
#stat: resurrectFuture(resurrectFileStat),
#statSync: resurrectFileStat,
#identical: resurrectFuture(resurrectPassthrough),
#identicalSync: resurrectPassthrough,
#type: resurrectFuture(resurrectFileSystemEntityType),
#typeSync: resurrectFileSystemEntityType,
});
properties.addAll(<Symbol, Resurrector>{
#path: resurrectPathContext,
#systemTempDirectory: resurrectDirectory(this),
#currentDirectory: resurrectDirectory(this),
const Symbol('currentDirectory='): resurrectPassthrough,
#isWatchSupported: resurrectPassthrough,
});
}
@override
String get identifier => kFileSystemEncodedValue;
@override
final List<Map<String, dynamic>> manifest;
}