Monotonic clock version of MemoryFileSystem (#129)
diff --git a/packages/file/lib/src/backends/memory/clock.dart b/packages/file/lib/src/backends/memory/clock.dart
new file mode 100644
index 0000000..12549aa
--- /dev/null
+++ b/packages/file/lib/src/backends/memory/clock.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, 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.
+
+/// Interface describing clocks used by the [MemoryFileSystem].
+///
+/// The [MemoryFileSystem] uses a clock to determine the modification times of
+/// files that are created in that file system.
+abstract class Clock {
+ /// Abstract const constructor. This constructor enables subclasses to provide
+ /// const constructors so that they can be used in const expressions.
+ const Clock();
+
+ /// A real-time clock.
+ ///
+ /// Uses [DateTime.now] to reflect the actual time reported by the operating
+ /// system.
+ const factory Clock.realTime() = _RealtimeClock;
+
+ /// A monotonically-increasing test clock.
+ ///
+ /// Each time [now] is called, the time increases by one minute.
+ ///
+ /// The `start` argument can be used to set the seed time for the clock.
+ /// The first value will be that time plus one minute.
+ /// By default, `start` is midnight on the first of January, 2000.
+ factory Clock.monotonicTest() = _MonotonicTestClock;
+
+ /// Returns the value of the clock.
+ DateTime get now;
+}
+
+class _RealtimeClock extends Clock {
+ const _RealtimeClock();
+
+ @override
+ DateTime get now => DateTime.now();
+}
+
+class _MonotonicTestClock extends Clock {
+ _MonotonicTestClock({
+ DateTime start,
+ }) : _current = start ?? DateTime(2000);
+
+ DateTime _current;
+
+ @override
+ DateTime get now {
+ _current = _current.add(const Duration(minutes: 1));
+ return _current;
+ }
+}
diff --git a/packages/file/lib/src/backends/memory/memory_file_system.dart b/packages/file/lib/src/backends/memory/memory_file_system.dart
index e5e09b8..38f818e 100644
--- a/packages/file/lib/src/backends/memory/memory_file_system.dart
+++ b/packages/file/lib/src/backends/memory/memory_file_system.dart
@@ -6,8 +6,10 @@
import 'package:file/file.dart';
import 'package:file/src/io.dart' as io;
+import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
+import 'clock.dart';
import 'common.dart';
import 'memory_directory.dart';
import 'memory_file.dart';
@@ -35,16 +37,46 @@
/// The file system will be empty, and the current directory will be the
/// root directory.
///
+ /// The clock will be a real-time clock; file modification times will
+ /// reflect the real time as reported by the operating system.
+ ///
/// If [style] is specified, the file system will use the specified path
/// style. The default is [FileSystemStyle.posix].
- factory MemoryFileSystem({FileSystemStyle style}) = _MemoryFileSystem;
+ factory MemoryFileSystem({
+ FileSystemStyle style = FileSystemStyle.posix,
+ }) =>
+ _MemoryFileSystem(
+ style: style,
+ clock: const Clock.realTime(),
+ );
+
+ /// Creates a new `MemoryFileSystem` that has a fake clock.
+ ///
+ /// The file system will be empty, and the current directory will be the
+ /// root directory.
+ ///
+ /// The clock will increase monotonically each time it is used, disconnected
+ /// from any real-world clock.
+ ///
+ /// If [style] is specified, the file system will use the specified path
+ /// style. The default is [FileSystemStyle.posix].
+ factory MemoryFileSystem.test({
+ FileSystemStyle style = FileSystemStyle.posix,
+ }) =>
+ _MemoryFileSystem(
+ style: style,
+ clock: Clock.monotonicTest(),
+ );
}
/// Internal implementation of [MemoryFileSystem].
class _MemoryFileSystem extends FileSystem
implements MemoryFileSystem, NodeBasedFileSystem {
- _MemoryFileSystem({this.style = FileSystemStyle.posix})
- : assert(style != null) {
+ _MemoryFileSystem({
+ this.style = FileSystemStyle.posix,
+ @required this.clock,
+ }) : assert(style != null),
+ assert(clock != null) {
_root = RootNode(this);
_context = style.contextFor(style.root);
}
@@ -54,6 +86,9 @@
p.Context _context;
@override
+ final Clock clock;
+
+ @override
final FileSystemStyle style;
@override
diff --git a/packages/file/lib/src/backends/memory/node.dart b/packages/file/lib/src/backends/memory/node.dart
index e053aec..68c4cc9 100644
--- a/packages/file/lib/src/backends/memory/node.dart
+++ b/packages/file/lib/src/backends/memory/node.dart
@@ -7,6 +7,7 @@
import 'package:file/file.dart';
import 'package:file/src/io.dart' as io;
+import 'clock.dart';
import 'common.dart';
import 'memory_file_stat.dart';
import 'style.dart';
@@ -46,6 +47,10 @@
/// The path of the current working directory.
String get cwd;
+ /// The clock to use when finding the current time (e.g. to set the creation
+ /// time of a new node).
+ Clock get clock;
+
/// Gets the backing node of the entity at the specified path. If the tail
/// element of the path does not exist, this will return null. If the tail
/// element cannot be reached because its directory does not exist, a
@@ -142,12 +147,14 @@
abstract class RealNode extends Node {
/// Constructs a new [RealNode] as a child of the specified [parent].
RealNode(DirectoryNode parent) : super(parent) {
- int now = DateTime.now().millisecondsSinceEpoch;
+ int now = clock.now.millisecondsSinceEpoch;
changed = now;
modified = now;
accessed = now;
}
+ Clock get clock => parent.clock;
+
/// Last changed time in milliseconds since the Epoch.
int changed;
@@ -177,7 +184,7 @@
/// Updates the last modified time of the node.
void touch() {
- modified = DateTime.now().millisecondsSinceEpoch;
+ modified = clock.now.millisecondsSinceEpoch;
}
}
@@ -211,6 +218,9 @@
final NodeBasedFileSystem fs;
@override
+ Clock get clock => fs.clock;
+
+ @override
DirectoryNode get parent => this;
@override
@@ -253,7 +263,7 @@
/// fields will be reset as opposed to copied to indicate that this file
/// has been modified and changed.
void copyFrom(FileNode source) {
- modified = changed = DateTime.now().millisecondsSinceEpoch;
+ modified = changed = clock.now.millisecondsSinceEpoch;
accessed = source.accessed;
mode = source.mode;
_content = Uint8List.fromList(source.content);
diff --git a/packages/file/test/memory_test.dart b/packages/file/test/memory_test.dart
index ed2c9b2..dfa76bf 100644
--- a/packages/file/test/memory_test.dart
+++ b/packages/file/test/memory_test.dart
@@ -67,4 +67,33 @@
});
});
});
+
+ test('MemoryFileSystem.test', () {
+ final MemoryFileSystem fs =
+ MemoryFileSystem.test(); // creates root directory
+ fs.file('/test1.txt').createSync(); // creates file
+ fs.file('/test2.txt').createSync(); // creates file
+ expect(fs.directory('/').statSync().modified, DateTime(2000, 1, 1, 0, 1));
+ expect(
+ fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 2));
+ expect(
+ fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 3));
+ fs.file('/test1.txt').createSync();
+ fs.file('/test2.txt').createSync();
+ expect(fs.file('/test1.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 2)); // file already existed
+ expect(fs.file('/test2.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 3)); // file already existed
+ fs.file('/test1.txt').writeAsStringSync('test'); // touches file
+ expect(
+ fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 4));
+ expect(fs.file('/test2.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 3)); // didn't touch it
+ fs.file('/test1.txt').copySync(
+ '/test2.txt'); // creates file, then mutates file (so time changes twice)
+ expect(fs.file('/test1.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 4)); // didn't touch it
+ expect(
+ fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 6));
+ });
}