Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit d35d82d

Browse files
Hixietvolkert
authored andcommitted
Monotonic clock version of MemoryFileSystem (#129)
1 parent c70c123 commit d35d82d

File tree

4 files changed

+132
-6
lines changed

4 files changed

+132
-6
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Interface describing clocks used by the [MemoryFileSystem].
6+
///
7+
/// The [MemoryFileSystem] uses a clock to determine the modification times of
8+
/// files that are created in that file system.
9+
abstract class Clock {
10+
/// Abstract const constructor. This constructor enables subclasses to provide
11+
/// const constructors so that they can be used in const expressions.
12+
const Clock();
13+
14+
/// A real-time clock.
15+
///
16+
/// Uses [DateTime.now] to reflect the actual time reported by the operating
17+
/// system.
18+
const factory Clock.realTime() = _RealtimeClock;
19+
20+
/// A monotonically-increasing test clock.
21+
///
22+
/// Each time [now] is called, the time increases by one minute.
23+
///
24+
/// The `start` argument can be used to set the seed time for the clock.
25+
/// The first value will be that time plus one minute.
26+
/// By default, `start` is midnight on the first of January, 2000.
27+
factory Clock.monotonicTest() = _MonotonicTestClock;
28+
29+
/// Returns the value of the clock.
30+
DateTime get now;
31+
}
32+
33+
class _RealtimeClock extends Clock {
34+
const _RealtimeClock();
35+
36+
@override
37+
DateTime get now => DateTime.now();
38+
}
39+
40+
class _MonotonicTestClock extends Clock {
41+
_MonotonicTestClock({
42+
DateTime start,
43+
}) : _current = start ?? DateTime(2000);
44+
45+
DateTime _current;
46+
47+
@override
48+
DateTime get now {
49+
_current = _current.add(const Duration(minutes: 1));
50+
return _current;
51+
}
52+
}

packages/file/lib/src/backends/memory/memory_file_system.dart

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import 'dart:async';
66

77
import 'package:file/file.dart';
88
import 'package:file/src/io.dart' as io;
9+
import 'package:meta/meta.dart';
910
import 'package:path/path.dart' as p;
1011

12+
import 'clock.dart';
1113
import 'common.dart';
1214
import 'memory_directory.dart';
1315
import 'memory_file.dart';
@@ -35,16 +37,46 @@ abstract class MemoryFileSystem implements StyleableFileSystem {
3537
/// The file system will be empty, and the current directory will be the
3638
/// root directory.
3739
///
40+
/// The clock will be a real-time clock; file modification times will
41+
/// reflect the real time as reported by the operating system.
42+
///
43+
/// If [style] is specified, the file system will use the specified path
44+
/// style. The default is [FileSystemStyle.posix].
45+
factory MemoryFileSystem({
46+
FileSystemStyle style = FileSystemStyle.posix,
47+
}) =>
48+
_MemoryFileSystem(
49+
style: style,
50+
clock: const Clock.realTime(),
51+
);
52+
53+
/// Creates a new `MemoryFileSystem` that has a fake clock.
54+
///
55+
/// The file system will be empty, and the current directory will be the
56+
/// root directory.
57+
///
58+
/// The clock will increase monotonically each time it is used, disconnected
59+
/// from any real-world clock.
60+
///
3861
/// If [style] is specified, the file system will use the specified path
3962
/// style. The default is [FileSystemStyle.posix].
40-
factory MemoryFileSystem({FileSystemStyle style}) = _MemoryFileSystem;
63+
factory MemoryFileSystem.test({
64+
FileSystemStyle style = FileSystemStyle.posix,
65+
}) =>
66+
_MemoryFileSystem(
67+
style: style,
68+
clock: Clock.monotonicTest(),
69+
);
4170
}
4271

4372
/// Internal implementation of [MemoryFileSystem].
4473
class _MemoryFileSystem extends FileSystem
4574
implements MemoryFileSystem, NodeBasedFileSystem {
46-
_MemoryFileSystem({this.style = FileSystemStyle.posix})
47-
: assert(style != null) {
75+
_MemoryFileSystem({
76+
this.style = FileSystemStyle.posix,
77+
@required this.clock,
78+
}) : assert(style != null),
79+
assert(clock != null) {
4880
_root = RootNode(this);
4981
_context = style.contextFor(style.root);
5082
}
@@ -53,6 +85,9 @@ class _MemoryFileSystem extends FileSystem
5385
String _systemTemp;
5486
p.Context _context;
5587

88+
@override
89+
final Clock clock;
90+
5691
@override
5792
final FileSystemStyle style;
5893

packages/file/lib/src/backends/memory/node.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:typed_data';
77
import 'package:file/file.dart';
88
import 'package:file/src/io.dart' as io;
99

10+
import 'clock.dart';
1011
import 'common.dart';
1112
import 'memory_file_stat.dart';
1213
import 'style.dart';
@@ -46,6 +47,10 @@ abstract class NodeBasedFileSystem implements StyleableFileSystem {
4647
/// The path of the current working directory.
4748
String get cwd;
4849

50+
/// The clock to use when finding the current time (e.g. to set the creation
51+
/// time of a new node).
52+
Clock get clock;
53+
4954
/// Gets the backing node of the entity at the specified path. If the tail
5055
/// element of the path does not exist, this will return null. If the tail
5156
/// element cannot be reached because its directory does not exist, a
@@ -142,12 +147,14 @@ abstract class Node {
142147
abstract class RealNode extends Node {
143148
/// Constructs a new [RealNode] as a child of the specified [parent].
144149
RealNode(DirectoryNode parent) : super(parent) {
145-
int now = DateTime.now().millisecondsSinceEpoch;
150+
int now = clock.now.millisecondsSinceEpoch;
146151
changed = now;
147152
modified = now;
148153
accessed = now;
149154
}
150155

156+
Clock get clock => parent.clock;
157+
151158
/// Last changed time in milliseconds since the Epoch.
152159
int changed;
153160

@@ -177,7 +184,7 @@ abstract class RealNode extends Node {
177184

178185
/// Updates the last modified time of the node.
179186
void touch() {
180-
modified = DateTime.now().millisecondsSinceEpoch;
187+
modified = clock.now.millisecondsSinceEpoch;
181188
}
182189
}
183190

@@ -210,6 +217,9 @@ class RootNode extends DirectoryNode {
210217
@override
211218
final NodeBasedFileSystem fs;
212219

220+
@override
221+
Clock get clock => fs.clock;
222+
213223
@override
214224
DirectoryNode get parent => this;
215225

@@ -253,7 +263,7 @@ class FileNode extends RealNode {
253263
/// fields will be reset as opposed to copied to indicate that this file
254264
/// has been modified and changed.
255265
void copyFrom(FileNode source) {
256-
modified = changed = DateTime.now().millisecondsSinceEpoch;
266+
modified = changed = clock.now.millisecondsSinceEpoch;
257267
accessed = source.accessed;
258268
mode = source.mode;
259269
_content = Uint8List.fromList(source.content);

packages/file/test/memory_test.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,33 @@ void main() {
6767
});
6868
});
6969
});
70+
71+
test('MemoryFileSystem.test', () {
72+
final MemoryFileSystem fs =
73+
MemoryFileSystem.test(); // creates root directory
74+
fs.file('/test1.txt').createSync(); // creates file
75+
fs.file('/test2.txt').createSync(); // creates file
76+
expect(fs.directory('/').statSync().modified, DateTime(2000, 1, 1, 0, 1));
77+
expect(
78+
fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 2));
79+
expect(
80+
fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 3));
81+
fs.file('/test1.txt').createSync();
82+
fs.file('/test2.txt').createSync();
83+
expect(fs.file('/test1.txt').statSync().modified,
84+
DateTime(2000, 1, 1, 0, 2)); // file already existed
85+
expect(fs.file('/test2.txt').statSync().modified,
86+
DateTime(2000, 1, 1, 0, 3)); // file already existed
87+
fs.file('/test1.txt').writeAsStringSync('test'); // touches file
88+
expect(
89+
fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 4));
90+
expect(fs.file('/test2.txt').statSync().modified,
91+
DateTime(2000, 1, 1, 0, 3)); // didn't touch it
92+
fs.file('/test1.txt').copySync(
93+
'/test2.txt'); // creates file, then mutates file (so time changes twice)
94+
expect(fs.file('/test1.txt').statSync().modified,
95+
DateTime(2000, 1, 1, 0, 4)); // didn't touch it
96+
expect(
97+
fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 6));
98+
});
7099
}

0 commit comments

Comments
 (0)