Update API per new dart:io methods (#39)
diff --git a/.analysis_options b/.analysis_options
index b0163e6..bf6018c 100644
--- a/.analysis_options
+++ b/.analysis_options
@@ -4,6 +4,8 @@
enableStrictCallChecks: true
enableSuperMixins: true
errors:
+ # treat missing required parameters as a warning (not a hint)
+ missing_required_param: warning
# Allow having TODOs in the code
todo: ignore
@@ -60,7 +62,7 @@
- super_goes_last
- type_annotate_public_apis
- type_init_formals
- - unawaited_futures
+ # TODO - unawaited_futures (https://github.com/dart-lang/linter/issues/419)
- unnecessary_brace_in_string_interp
- unnecessary_getters_setters
diff --git a/lib/src/backends/chroot/chroot_file.dart b/lib/src/backends/chroot/chroot_file.dart
index 244b7fa..74d8cc9 100644
--- a/lib/src/backends/chroot/chroot_file.dart
+++ b/lib/src/backends/chroot/chroot_file.dart
@@ -209,6 +209,22 @@
int lengthSync() => getDelegate(followLinks: true).lengthSync();
@override
+ Future<DateTime> lastAccessed() =>
+ getDelegate(followLinks: true).lastAccessed();
+
+ @override
+ DateTime lastAccessedSync() =>
+ getDelegate(followLinks: true).lastAccessedSync();
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) =>
+ getDelegate(followLinks: true).setLastAccessed(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) =>
+ getDelegate(followLinks: true).setLastAccessedSync(time);
+
+ @override
Future<DateTime> lastModified() =>
getDelegate(followLinks: true).lastModified();
@@ -217,6 +233,14 @@
getDelegate(followLinks: true).lastModifiedSync();
@override
+ Future<dynamic> setLastModified(DateTime time) =>
+ getDelegate(followLinks: true).setLastModified(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) =>
+ getDelegate(followLinks: true).setLastModifiedSync(time);
+
+ @override
Future<RandomAccessFile> open({
FileMode mode: FileMode.READ,
}) async =>
diff --git a/lib/src/backends/memory/memory_file.dart b/lib/src/backends/memory/memory_file.dart
index 66e5abc..ded6bfa 100644
--- a/lib/src/backends/memory/memory_file.dart
+++ b/lib/src/backends/memory/memory_file.dart
@@ -122,12 +122,38 @@
File get absolute => super.absolute;
@override
+ Future<DateTime> lastAccessed() async => lastAccessedSync();
+
+ @override
+ DateTime lastAccessedSync() => (_resolvedBacking as _FileNode).stat.accessed;
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) async =>
+ setLastAccessedSync(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) {
+ _FileNode node = _resolvedBacking;
+ node.accessed = time.millisecondsSinceEpoch;
+ }
+
+ @override
Future<DateTime> lastModified() async => lastModifiedSync();
@override
DateTime lastModifiedSync() => (_resolvedBacking as _FileNode).stat.modified;
@override
+ Future<dynamic> setLastModified(DateTime time) async =>
+ setLastModifiedSync(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) {
+ _FileNode node = _resolvedBacking;
+ node.modified = time.millisecondsSinceEpoch;
+ }
+
+ @override
Future<io.RandomAccessFile> open(
{io.FileMode mode: io.FileMode.READ}) async =>
openSync(mode: mode);
diff --git a/lib/src/backends/record_replay/recording_file.dart b/lib/src/backends/record_replay/recording_file.dart
index 1453b7e..4a9ea43 100644
--- a/lib/src/backends/record_replay/recording_file.dart
+++ b/lib/src/backends/record_replay/recording_file.dart
@@ -43,8 +43,14 @@
#copySync: _copySync,
#length: delegate.length,
#lengthSync: delegate.lengthSync,
+ #lastAccessed: delegate.lastAccessed,
+ #lastAccessedSync: delegate.lastAccessedSync,
+ #setLastAccessed: delegate.setLastAccessed,
+ #setLastAccessedSync: delegate.setLastAccessedSync,
#lastModified: delegate.lastModified,
#lastModifiedSync: delegate.lastModifiedSync,
+ #setLastModified: delegate.setLastModified,
+ #setLastModifiedSync: delegate.setLastModifiedSync,
#open: _open,
#openSync: _openSync,
#openRead: _openRead,
diff --git a/lib/src/backends/record_replay/replay_file.dart b/lib/src/backends/record_replay/replay_file.dart
index 1995f31..9b26a2c 100644
--- a/lib/src/backends/record_replay/replay_file.dart
+++ b/lib/src/backends/record_replay/replay_file.dart
@@ -42,8 +42,14 @@
#copySync: reviveFile,
#length: const ToFuture<int>(),
#lengthSync: const Passthrough<int>(),
+ #lastAccessed: DateTimeCodec.deserialize.fuse(const ToFuture<DateTime>()),
+ #lastAccessedSync: DateTimeCodec.deserialize,
+ #setLastAccessed: const ToFuture<dynamic>(),
+ #setLastAccessedSync: const Passthrough<Null>(),
#lastModified: DateTimeCodec.deserialize.fuse(const ToFuture<DateTime>()),
#lastModifiedSync: DateTimeCodec.deserialize,
+ #setLastModified: const ToFuture<dynamic>(),
+ #setLastModifiedSync: const Passthrough<Null>(),
#open: reviveRandomAccessFile.fuse(const ToFuture<RandomAccessFile>()),
#openSync: reviveRandomAccessFile,
#openRead: blobToByteStream,
diff --git a/lib/src/forwarding/forwarding_file.dart b/lib/src/forwarding/forwarding_file.dart
index 500642d..19dc3fb 100644
--- a/lib/src/forwarding/forwarding_file.dart
+++ b/lib/src/forwarding/forwarding_file.dart
@@ -31,12 +31,32 @@
int lengthSync() => delegate.lengthSync();
@override
+ Future<DateTime> lastAccessed() => delegate.lastAccessed();
+
+ @override
+ DateTime lastAccessedSync() => delegate.lastAccessedSync();
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) =>
+ delegate.setLastAccessed(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) => delegate.setLastAccessedSync(time);
+
+ @override
Future<DateTime> lastModified() => delegate.lastModified();
@override
DateTime lastModifiedSync() => delegate.lastModifiedSync();
@override
+ Future<dynamic> setLastModified(DateTime time) =>
+ delegate.setLastModified(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) => delegate.setLastModifiedSync(time);
+
+ @override
Future<RandomAccessFile> open({
FileMode mode: FileMode.READ,
}) async =>
diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart
index 46777aa..78bb899 100644
--- a/lib/src/io/interface.dart
+++ b/lib/src/io/interface.dart
@@ -67,12 +67,30 @@
File get absolute;
// ignore: public_member_api_docs
+ Future<DateTime> lastAccessed();
+
+ // ignore: public_member_api_docs
+ DateTime lastAccessedSync();
+
+ // ignore: public_member_api_docs
+ Future<dynamic> setLastAccessed(DateTime time);
+
+ // ignore: public_member_api_docs
+ void setLastAccessedSync(DateTime time);
+
+ // ignore: public_member_api_docs
Future<DateTime> lastModified();
// ignore: public_member_api_docs
DateTime lastModifiedSync();
// ignore: public_member_api_docs
+ Future<dynamic> setLastModified(DateTime time);
+
+ // ignore: public_member_api_docs
+ void setLastModifiedSync(DateTime time);
+
+ // ignore: public_member_api_docs
Future<RandomAccessFile> open({FileMode mode: FileMode.READ});
// ignore: public_member_api_docs
diff --git a/test/chroot_test.dart b/test/chroot_test.dart
index 995d08b..f9e0d62 100644
--- a/test/chroot_test.dart
+++ b/test/chroot_test.dart
@@ -48,6 +48,11 @@
runCommonTests(
() => fs,
skip: <String>[
+ // API doesn't exit in dart:io until Dart 1.23
+ 'File > lastAccessed',
+ 'File > setLastAccessed',
+ 'File > setLastModified',
+
// https://github.com/dart-lang/sdk/issues/28170
'File > create > throwsIfAlreadyExistsAsDirectory',
'File > create > throwsIfAlreadyExistsAsLinkToDirectory',
diff --git a/test/common_tests.dart b/test/common_tests.dart
index 630af33..867ba95 100644
--- a/test/common_tests.dart
+++ b/test/common_tests.dart
@@ -12,6 +12,8 @@
import 'package:test/test.dart';
import 'package:test/test.dart' as testpkg show group, setUp, tearDown, test;
+import 'utils.dart';
+
/// Callback used in [runCommonTests] to produce the root folder in which all
/// file system entities will be created.
typedef String RootPathGenerator();
@@ -1483,11 +1485,78 @@
});
});
+ group('lastAccessed', () {
+ test('isNowForNewlyCreatedFile', () {
+ DateTime before = floor();
+ File f = fs.file(ns('/foo'))..createSync();
+ DateTime after = ceil();
+ DateTime accessed = f.lastAccessedSync();
+ expect(before, isSameOrBefore(accessed));
+ expect(after, isSameOrAfter(accessed));
+ });
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException('No such file or directory', () {
+ fs.file(ns('/foo')).lastAccessedSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException('Is a directory', () {
+ fs.file(ns('/foo')).lastAccessedSync();
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ DateTime before = floor();
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ DateTime after = ceil();
+ DateTime accessed = fs.file(ns('/bar')).lastAccessedSync();
+ expect(before, isSameOrBefore(accessed));
+ expect(after, isSameOrAfter(accessed));
+ });
+ });
+
+ group('setLastAccessed', () {
+ final DateTime time = new DateTime(1999);
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException('No such file or directory', () {
+ fs.file(ns('/foo')).setLastAccessedSync(time);
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException('Is a directory', () {
+ fs.file(ns('/foo')).setLastAccessedSync(time);
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ File f = fs.file(ns('/foo'))..createSync();
+ f.setLastAccessedSync(time);
+ expect(fs.file(ns('/foo')).lastAccessedSync(), time);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ File f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.setLastAccessedSync(time);
+ expect(fs.file(ns('/bar')).lastAccessedSync(), time);
+ });
+ });
+
group('lastModified', () {
test('isNowForNewlyCreatedFile', () {
+ DateTime before = floor();
File f = fs.file(ns('/foo'))..createSync();
- expect(new DateTime.now().difference(f.lastModifiedSync()).abs(),
- lessThan(new Duration(seconds: 2)));
+ DateTime after = ceil();
+ DateTime modified = f.lastModifiedSync();
+ expect(before, isSameOrBefore(modified));
+ expect(after, isSameOrAfter(modified));
});
test('throwsIfDoesntExist', () {
@@ -1504,14 +1573,43 @@
});
test('succeedsIfExistsAsLinkToFile', () {
+ DateTime before = floor();
fs.file(ns('/foo')).createSync();
fs.link(ns('/bar')).createSync(ns('/foo'));
- expect(
- new DateTime.now()
- .difference(fs.file(ns('/bar')).lastModifiedSync())
- .abs(),
- lessThan(new Duration(seconds: 2)),
- );
+ DateTime after = ceil();
+ DateTime modified = fs.file(ns('/bar')).lastModifiedSync();
+ expect(before, isSameOrBefore(modified));
+ expect(after, isSameOrAfter(modified));
+ });
+ });
+
+ group('setLastModified', () {
+ final DateTime time = new DateTime(1999);
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException('No such file or directory', () {
+ fs.file(ns('/foo')).setLastModifiedSync(time);
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException('Is a directory', () {
+ fs.file(ns('/foo')).setLastModifiedSync(time);
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ File f = fs.file(ns('/foo'))..createSync();
+ f.setLastModifiedSync(time);
+ expect(fs.file(ns('/foo')).lastModifiedSync(), time);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ File f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.setLastModifiedSync(time);
+ expect(fs.file(ns('/bar')).lastModifiedSync(), time);
});
});
diff --git a/test/local_test.dart b/test/local_test.dart
index 0e893ab..028b4e5 100644
--- a/test/local_test.dart
+++ b/test/local_test.dart
@@ -33,6 +33,11 @@
() => fs,
root: () => tmp.path,
skip: <String>[
+ // API doesn't exit in dart:io until Dart 1.23
+ 'File > lastAccessed',
+ 'File > setLastAccessed',
+ 'File > setLastModified',
+
// https://github.com/dart-lang/sdk/issues/28170
'File > create > throwsIfAlreadyExistsAsDirectory',
'File > create > throwsIfAlreadyExistsAsLinkToDirectory',
diff --git a/test/utils.dart b/test/utils.dart
new file mode 100644
index 0000000..40e7b3b
--- /dev/null
+++ b/test/utils.dart
@@ -0,0 +1,95 @@
+// 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 'package:meta/meta.dart';
+import 'package:test/test.dart';
+
+/// Returns a [DateTime] with an exact second-precision by removing the
+/// milliseconds and microseconds from the specified [time].
+///
+/// If [time] is not specified, it will default to the current time.
+DateTime floor([DateTime time]) {
+ time ??= new DateTime.now();
+ return time.subtract(new Duration(
+ milliseconds: time.millisecond,
+ microseconds: time.microsecond,
+ ));
+}
+
+/// Returns a [DateTime] with an exact second precision by adding just enough
+/// milliseconds and microseconds to the specified [time] to reach the next
+/// second.
+///
+/// If [time] is not specified, it will default to the current time.
+DateTime ceil([DateTime time]) {
+ time ??= new DateTime.now();
+ int microseconds = (1000 * time.millisecond) + time.microsecond;
+ return time.add(new Duration(microseconds: 1000000 - microseconds));
+}
+
+/// Successfully matches against a [DateTime] that is the same moment or before
+/// the specified [time].
+Matcher isSameOrBefore(DateTime time) => new _IsSameOrBefore(time);
+
+/// Successfully matches against a [DateTime] that is the same moment or after
+/// the specified [time].
+Matcher isSameOrAfter(DateTime time) => new _IsSameOrAfter(time);
+
+abstract class _CompareDateTime extends Matcher {
+ final DateTime _time;
+ final Matcher _matcher;
+
+ const _CompareDateTime(this._time, this._matcher);
+
+ @override
+ bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
+ return item is DateTime &&
+ _matcher.matches(item.compareTo(_time), <dynamic, dynamic>{});
+ }
+
+ @protected
+ String get descriptionOperator;
+
+ @override
+ Description describe(Description description) =>
+ description.add('a DateTime $descriptionOperator $_time');
+
+ @protected
+ String get mismatchAdjective;
+
+ @override
+ Description describeMismatch(
+ dynamic item,
+ Description description,
+ Map<dynamic, dynamic> matchState,
+ bool verbose,
+ ) {
+ if (item is DateTime) {
+ Duration diff = item.difference(_time).abs();
+ return description.add('is $mismatchAdjective $_time by $diff');
+ } else {
+ return description.add('is not a DateTime');
+ }
+ }
+}
+
+class _IsSameOrBefore extends _CompareDateTime {
+ const _IsSameOrBefore(DateTime time) : super(time, isNonPositive);
+
+ @override
+ String get descriptionOperator => '<=';
+
+ @override
+ String get mismatchAdjective => 'after';
+}
+
+class _IsSameOrAfter extends _CompareDateTime {
+ const _IsSameOrAfter(DateTime time) : super(time, isNonNegative);
+
+ @override
+ String get descriptionOperator => '>=';
+
+ @override
+ String get mismatchAdjective => 'before';
+}
diff --git a/test/utils_test.dart b/test/utils_test.dart
new file mode 100644
index 0000000..fb6f079
--- /dev/null
+++ b/test/utils_test.dart
@@ -0,0 +1,35 @@
+// 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 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('floorAndCeilProduceExactSecondDateTime', () {
+ DateTime time = new DateTime.fromMicrosecondsSinceEpoch(1001);
+ DateTime lower = floor(time);
+ DateTime upper = ceil(time);
+ expect(lower.millisecond, 0);
+ expect(upper.millisecond, 0);
+ expect(lower.microsecond, 0);
+ expect(upper.microsecond, 0);
+ });
+
+ test('floorAndCeilWorkWithNow', () {
+ DateTime time = new DateTime.now();
+ int lower = time.difference(floor(time)).inMicroseconds;
+ int upper = ceil(time).difference(time).inMicroseconds;
+ expect(lower, lessThan(1000000));
+ expect(upper, lessThanOrEqualTo(1000000));
+ });
+
+ test('floorAndCeilWorkWithExactSecondDateTime', () {
+ DateTime time = DateTime.parse('1999-12-31 23:59:59');
+ int lower = time.difference(floor(time)).inMicroseconds;
+ int upper = ceil(time).difference(time).inMicroseconds;
+ expect(lower, 0);
+ expect(upper, lessThanOrEqualTo(1000000));
+ });
+}