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

Monotonic clock version of MemoryFileSystem #129

Merged
merged 2 commits into from
Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/file/lib/src/backends/memory/clock.dart
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document what the default start value is.

/// 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;
}
}
41 changes: 38 additions & 3 deletions packages/file/lib/src/backends/memory/memory_file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import 'dart:async';

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';
Expand Down Expand Up @@ -35,16 +37,46 @@ abstract class MemoryFileSystem implements StyleableFileSystem {
/// 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 = 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({FileSystemStyle style}) = _MemoryFileSystem;
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);
}
Expand All @@ -53,6 +85,9 @@ class _MemoryFileSystem extends FileSystem
String _systemTemp;
p.Context _context;

@override
final Clock clock;

@override
final FileSystemStyle style;

Expand Down
16 changes: 13 additions & 3 deletions packages/file/lib/src/backends/memory/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:typed_data';
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';
Expand Down Expand Up @@ -46,6 +47,10 @@ abstract class NodeBasedFileSystem implements StyleableFileSystem {
/// 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
Expand Down Expand Up @@ -142,12 +147,14 @@ abstract class Node {
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;

Expand Down Expand Up @@ -177,7 +184,7 @@ abstract class RealNode extends Node {

/// Updates the last modified time of the node.
void touch() {
modified = DateTime.now().millisecondsSinceEpoch;
modified = clock.now.millisecondsSinceEpoch;
}
}

Expand Down Expand Up @@ -210,6 +217,9 @@ class RootNode extends DirectoryNode {
@override
final NodeBasedFileSystem fs;

@override
Clock get clock => fs.clock;

@override
DirectoryNode get parent => this;

Expand Down Expand Up @@ -253,7 +263,7 @@ class FileNode extends RealNode {
/// 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);
Expand Down
29 changes: 29 additions & 0 deletions packages/file/test/memory_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,33 @@ void main() {
});
});
});

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));
});
}