diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 6e54b1f5c..f9f0ea8b6 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.1.5-wip +- Polling watchers now check file sizes as well as "last modified" times, so + they are less likely to miss changes on platforms with low resolution + timestamps. - Bug fix: with `FileWatcher` on MacOS, a modify event was sometimes reported if the file was created immediately before the watcher was created. Now, if the file exists when the watcher is created then this modify event is not sent. diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 172fabeea..1a9a45552 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -7,12 +7,15 @@ import 'dart:io'; import '../async_queue.dart'; import '../directory_watcher.dart'; +import '../polling.dart'; import '../resubscribable.dart'; -import '../stat.dart'; import '../utils.dart'; import '../watch_event.dart'; /// Periodically polls a directory for changes. +/// +/// Changes are noticed if the "last modified" time of a file changes or if its +/// size changes. class PollingDirectoryWatcher extends ResubscribableWatcher implements DirectoryWatcher { @override @@ -53,10 +56,7 @@ class _PollingDirectoryWatcher /// directory contents. final Duration _pollingDelay; - /// The previous modification times of the files in the directory. - /// - /// Used to tell which files have been modified. - final _lastModifieds = {}; + final _previousPollResults = {}; /// The subscription used while [directory] is being listed. /// @@ -78,7 +78,8 @@ class _PollingDirectoryWatcher /// The set of files that have been seen in the current directory listing. /// /// Used to tell which files have been removed: files that are in - /// [_lastModifieds] but not in here when a poll completes have been removed. + /// [_previousPollResults] but not in here when a poll completes have been + /// removed. final _polledFiles = {}; _PollingDirectoryWatcher(this.path, this._pollingDelay) { @@ -95,7 +96,7 @@ class _PollingDirectoryWatcher // Don't process any remaining files. _filesToProcess.clear(); _polledFiles.clear(); - _lastModifieds.clear(); + _previousPollResults.clear(); } /// Scans the contents of the directory once to see which files have been @@ -145,14 +146,14 @@ class _PollingDirectoryWatcher return; } - final modified = await modificationTime(file); + final pollResult = await PollResult.poll(file); if (_events.isClosed) return; - var lastModified = _lastModifieds[file]; + var previousPollResult = _previousPollResults[file]; // If its modification time hasn't changed, assume the file is unchanged. - if (lastModified != null && lastModified == modified) { + if (previousPollResult != null && previousPollResult == pollResult) { // The file is still here. _polledFiles.add(file); return; @@ -161,17 +162,17 @@ class _PollingDirectoryWatcher if (_events.isClosed) return; _polledFiles.add(file); - if (modified == null) { + if (!pollResult.fileExists) { // The file was in the directory listing but has been removed since then. - // Don't add to _lastModifieds, it will be reported as a REMOVE. + // Don't add to _previousPollResults, it will be reported as a REMOVE. return; } - _lastModifieds[file] = modified; + _previousPollResults[file] = pollResult; // Only notify if we're ready to emit events. if (!isReady) return; - var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY; + var type = previousPollResult == null ? ChangeType.ADD : ChangeType.MODIFY; _events.add(WatchEvent(type, file)); } @@ -180,10 +181,11 @@ class _PollingDirectoryWatcher Future _completePoll() async { // Any files that were not seen in the last poll but that we have a // status for must have been removed. - var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles); + var removedFiles = + _previousPollResults.keys.toSet().difference(_polledFiles); for (var removed in removedFiles) { if (isReady) _events.add(WatchEvent(ChangeType.REMOVE, removed)); - _lastModifieds.remove(removed); + _previousPollResults.remove(removed); } if (!isReady) _readyCompleter.complete(); diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 15ff9ab8e..e87649d67 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -6,8 +6,8 @@ import 'dart:async'; import 'dart:io'; import '../file_watcher.dart'; +import '../polling.dart'; import '../resubscribable.dart'; -import '../stat.dart'; import '../watch_event.dart'; /// Periodically polls a file for changes. @@ -37,10 +37,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { /// The timer that controls polling. late final Timer _timer; - /// The previous modification time of the file. - /// - /// `null` indicates the file does not (or did not on the last poll) exist. - DateTime? _lastModified; + PollResult _previousPollResult = PollResult.notAFile(); _PollingFileWatcher(this.path, Duration pollingDelay) { _timer = Timer.periodic(pollingDelay, (_) => _poll()); @@ -55,22 +52,23 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { var pathExists = await File(path).exists(); if (_eventsController.isClosed) return; - if (_lastModified != null && !pathExists) { + if (_previousPollResult.fileExists && !pathExists) { _flagReady(); _eventsController.add(WatchEvent(ChangeType.REMOVE, path)); unawaited(close()); return; } - DateTime? modified; + PollResult pollResult; try { - modified = await modificationTime(path); + pollResult = await PollResult.poll(path); } on FileSystemException catch (error, stackTrace) { if (!_eventsController.isClosed) { _flagReady(); _eventsController.addError(error, stackTrace); await close(); } + return; } if (_eventsController.isClosed) { _flagReady(); @@ -78,16 +76,16 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { } if (!isReady) { - // If this is the first poll, don't emit an event, just set the last mtime - // and complete the completer. - _lastModified = modified; + // If this is the first poll, don't emit an event, just set the poll + // result and complete the completer. + _previousPollResult = pollResult; _flagReady(); return; } - if (_lastModified == modified) return; + if (_previousPollResult == pollResult) return; - _lastModified = modified; + _previousPollResult = pollResult; _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } diff --git a/pkgs/watcher/lib/src/polling.dart b/pkgs/watcher/lib/src/polling.dart new file mode 100644 index 000000000..d3ff38886 --- /dev/null +++ b/pkgs/watcher/lib/src/polling.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2025, 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:io'; + +/// Result of polling a path. +/// +/// If it's a file, the result is combined from the file's "last modification" +/// time and size, so that a change to either can be noticed as a change. +/// +/// If the path is not a file, [fileExists] return `false`. +extension type PollResult._(int _value) { + /// A [PollResult] with [fileExists] `false`. + factory PollResult.notAFile() => PollResult._(0); + + static Future poll(String path) async { + final stat = await FileStat.stat(path); + if (stat.type != FileSystemEntityType.file) return PollResult.notAFile(); + + // Construct the poll result from the "last modified" time and size. + // It should be very likely to change if either changes. Both are 64 bit + // ints with the interesting bits in the low bits. Swap the 32 bit sections + // of `microseconds` so the interesting bits don't clash, then XOR them. + var microseconds = stat.modified.microsecondsSinceEpoch; + microseconds = microseconds << 32 | microseconds >>> 32; + return PollResult._(microseconds ^ stat.size); + } + + /// Whether the path exists and is a file. + bool get fileExists => _value != 0; +} diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart deleted file mode 100644 index 25019ef3b..000000000 --- a/pkgs/watcher/lib/src/stat.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2013, 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:io'; - -/// A function that takes a file path and returns the last modified time for -/// the file at that path. -typedef MockTimeCallback = DateTime? Function(String path); - -MockTimeCallback? _mockTimeCallback; - -/// Overrides the default behavior for accessing a file's modification time -/// with [callback]. -/// -/// The OS file modification time has pretty rough granularity (like a few -/// seconds) which can make for slow tests that rely on modtime. This lets you -/// replace it with something you control. -void mockGetModificationTime(MockTimeCallback? callback) { - _mockTimeCallback = callback; -} - -/// Gets the modification time for the file at [path]. -/// Completes with `null` if the file does not exist. -Future modificationTime(String path) async { - var mockTimeCallback = _mockTimeCallback; - if (mockTimeCallback != null) { - return mockTimeCallback(path); - } - - final stat = await FileStat.stat(path); - if (stat.type == FileSystemEntityType.notFound) return null; - return stat.modified; -} diff --git a/pkgs/watcher/test/directory_watcher/file_tests.dart b/pkgs/watcher/test/directory_watcher/file_tests.dart index 372ea4b27..77b41d966 100644 --- a/pkgs/watcher/test/directory_watcher/file_tests.dart +++ b/pkgs/watcher/test/directory_watcher/file_tests.dart @@ -67,6 +67,7 @@ void _fileTests({required bool isNative}) { writeFile('b.txt', contents: 'before'); await startWatcher(); + if (!isNative) sleepUntilNewModificationTime(); writeFile('a.txt', contents: 'same'); writeFile('b.txt', contents: 'after'); await inAnyOrder([isModifyEvent('a.txt'), isModifyEvent('b.txt')]); @@ -139,7 +140,7 @@ void _fileTests({required bool isNative}) { test('notifies when a file is moved onto an existing one', () async { writeFile('from.txt'); - writeFile('to.txt'); + writeFile('to.txt', contents: 'different'); await startWatcher(); renameFile('from.txt', 'to.txt'); diff --git a/pkgs/watcher/test/directory_watcher/link_tests.dart b/pkgs/watcher/test/directory_watcher/link_tests.dart index c323c9e54..3ca07f780 100644 --- a/pkgs/watcher/test/directory_watcher/link_tests.dart +++ b/pkgs/watcher/test/directory_watcher/link_tests.dart @@ -32,6 +32,7 @@ void _linkTests({required bool isNative}) { createDir('targets'); createDir('links'); writeFile('targets/a.target'); + sleepUntilNewModificationTime(); writeFile('targets/b.target'); writeLink(link: 'links/a.link', target: 'targets/a.target'); await startWatcher(path: 'links'); @@ -46,7 +47,7 @@ void _linkTests({required bool isNative}) { 'notifies when a link is replaced with a link to a different target ' 'with different contents', () async { writeFile('targets/a.target', contents: 'a'); - writeFile('targets/b.target', contents: 'b'); + writeFile('targets/b.target', contents: 'ab'); writeLink(link: 'links/a.link', target: 'targets/a.target'); await startWatcher(path: 'links'); diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 90e8c2368..78f6b78e5 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -15,63 +15,45 @@ import 'link_tests.dart'; void main() { // Use a short delay to make the tests run quickly. watcherFactory = (dir) => PollingDirectoryWatcher(dir, - pollingDelay: const Duration(milliseconds: 100)); - - // Filesystem modification times can be low resolution, mock them. - group('with mock mtime', () { - setUp(enableMockModificationTimes); - - fileTests(isNative: false); - linkTests(isNative: false); - - test('does not notify if the modification time did not change', () async { - writeFile('a.txt', contents: 'before'); - writeFile('b.txt', contents: 'before'); - await startWatcher(); - writeFile('a.txt', contents: 'after', updateModified: false); - writeFile('b.txt', contents: 'after'); - await expectModifyEvent('b.txt'); - }); - - // A poll does an async directory list then checks mtime on each file. Check - // handling of a file that is deleted between the two. - test('deletes during poll', () async { - await startWatcher(); - - for (var i = 0; i != 300; ++i) { - writeFile('$i'); - } - // A series of deletes with delays in between for 300ms, which will - // intersect with the 100ms polling multiple times. - for (var i = 0; i != 300; ++i) { - deleteFile('$i'); - await Future.delayed(const Duration(milliseconds: 1)); + pollingDelay: const Duration(milliseconds: 10)); + + /// See [enableSleepUntilNewModificationTime] for a note about the "polling" + /// tests. + setUp(enableSleepUntilNewModificationTime); + + fileTests(isNative: false); + linkTests(isNative: false); + + // A poll does an async directory list that runs "stat" on each file. Check + // handling of a file that is deleted between the two. + test('deletes during poll', () async { + await startWatcher(); + + for (var i = 0; i != 300; ++i) { + writeFile('$i'); + } + // A series of deletes with delays in between for 300ms, which will + // intersect with the 10ms polling multiple times. + for (var i = 0; i != 300; ++i) { + deleteFile('$i'); + await Future.delayed(const Duration(milliseconds: 1)); + } + + final events = + await takeEvents(duration: const Duration(milliseconds: 500)); + + // Events should be adds and removes that pair up, with no modify events. + final adds = {}; + final removes = {}; + for (var event in events) { + if (event.type == ChangeType.ADD) { + adds.add(event.path); + } else if (event.type == ChangeType.REMOVE) { + removes.add(event.path); + } else { + fail('Unexpected event: $event'); } - - final events = - await takeEvents(duration: const Duration(milliseconds: 500)); - - // Events should be adds and removes that pair up, with no modify events. - final adds = {}; - final removes = {}; - for (var event in events) { - if (event.type == ChangeType.ADD) { - adds.add(event.path); - } else if (event.type == ChangeType.REMOVE) { - removes.add(event.path); - } else { - fail('Unexpected event: $event'); - } - } - expect(adds, removes); - }); - }); - - // Also test with delayed writes and real mtimes. - group('with real mtime', () { - setUp(enableWaitingForDifferentModificationTimes); - - fileTests(isNative: false); - linkTests(isNative: false); + } + expect(adds, removes); }); } diff --git a/pkgs/watcher/test/file_watcher/file_tests.dart b/pkgs/watcher/test/file_watcher/file_tests.dart index 49d143879..7707625c8 100644 --- a/pkgs/watcher/test/file_watcher/file_tests.dart +++ b/pkgs/watcher/test/file_watcher/file_tests.dart @@ -46,6 +46,7 @@ void _fileTests({required bool isNative}) { test('notifies even if the file contents are unchanged', () async { await startWatcher(path: 'file.txt'); + if (!isNative) sleepUntilNewModificationTime(); writeFile('file.txt'); await expectModifyEvent('file.txt'); }); @@ -59,7 +60,7 @@ void _fileTests({required bool isNative}) { test( 'emits a modify event when another file is moved on top of the watched ' 'file', () async { - writeFile('old.txt'); + writeFile('old.txt', contents: 'different'); await startWatcher(path: 'file.txt'); renameFile('old.txt', 'file.txt'); await expectModifyEvent('file.txt'); @@ -94,7 +95,7 @@ void _fileTests({required bool isNative}) { await expectNoEvents(); writeFile('other_file.txt'); await expectModifyEvent('other_file.txt'); - writeFile('other_file.txt'); + writeFile('other_file.txt', contents: 'different'); await expectModifyEvent('other_file.txt'); } }); diff --git a/pkgs/watcher/test/file_watcher/link_tests.dart b/pkgs/watcher/test/file_watcher/link_tests.dart index ddab12c47..c11f48ec0 100644 --- a/pkgs/watcher/test/file_watcher/link_tests.dart +++ b/pkgs/watcher/test/file_watcher/link_tests.dart @@ -25,6 +25,7 @@ void _linkTests({required bool isNative}) { test('notifies when a link is overwritten with an identical file', () async { await startWatcher(path: 'link.txt'); + if (!isNative) sleepUntilNewModificationTime(); writeFile('link.txt'); // TODO(davidmorgan): reconcile differences. @@ -51,6 +52,7 @@ void _linkTests({required bool isNative}) { 'notifies when a link target is overwritten with an identical file', () async { await startWatcher(path: 'link.txt'); + if (!isNative) sleepUntilNewModificationTime(); writeFile('target.txt'); await expectModifyEvent('link.txt'); @@ -114,6 +116,7 @@ void _linkTests({required bool isNative}) { test('notifies when an identical file is moved over the link', () async { await startWatcher(path: 'link.txt'); + if (!isNative) sleepUntilNewModificationTime(); writeFile('old.txt'); renameFile('old.txt', 'link.txt'); @@ -140,6 +143,7 @@ void _linkTests({required bool isNative}) { test('notifies when an identical file is moved over the target', () async { await startWatcher(path: 'link.txt'); + if (!isNative) sleepUntilNewModificationTime(); writeFile('old.txt'); renameFile('old.txt', 'target.txt'); diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 3acb8a8ff..1ce13b9b8 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -12,23 +12,13 @@ import 'startup_race_tests.dart'; void main() { watcherFactory = (file) => - PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100)); + PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 10)); - // Filesystem modification times can be low resolution, mock them. - group('with mock mtime', () { - setUp(enableMockModificationTimes); + /// See [enableSleepUntilNewModificationTime] for a note about the "polling" + /// tests. + setUp(enableSleepUntilNewModificationTime); - fileTests(isNative: false); - linkTests(isNative: false); - startupRaceTests(isNative: false); - }); - -// Also test with delayed writes and real mtimes. - group('with real mtime', () { - setUp(enableWaitingForDifferentModificationTimes); - fileTests(isNative: false); - linkTests(isNative: false); - // Don't run `startupRaceTests`, polling can't have a race and the test is - // too slow on Windows when waiting for modification times. - }); + fileTests(isNative: false); + linkTests(isNative: false); + startupRaceTests(isNative: false); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 9a41b5ba6..2f723037c 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -9,7 +9,6 @@ import 'package:async/async.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; -import 'package:watcher/src/stat.dart'; import 'package:watcher/watcher.dart'; /// Edit this to run fast-running tests many times. @@ -22,19 +21,8 @@ set watcherFactory(WatcherFactory factory) { _watcherFactory = factory; } -/// The mock modification times (in milliseconds since epoch) for each file. -/// -/// The actual file system has pretty coarse granularity for file modification -/// times. This means using the real file system requires us to put delays in -/// the tests to ensure we wait long enough between operations for the mod time -/// to be different. -/// -/// Instead, we'll just mock that out. Each time a file is written, we manually -/// increment the mod time for that file instantly. -Map? _mockFileModificationTimes; - -/// If real modification times are used, a directory where a test file will be -/// updated to wait for a new modification time. +/// A directory where a test file will be updated to wait for a new modification +/// time. Directory? _waitForModificationTimesDirectory; late WatcherFactory _watcherFactory; @@ -61,12 +49,26 @@ late StreamQueue _watcherEvents; /// be done automatically via [addTearDown] in [startWatcher]. var _hasClosedStream = true; -/// Enables waiting before writes to ensure a different modification time. -/// -/// This will allow polling watchers to notice all writes. +/// Enables [sleepUntilNewModificationTime]. /// /// Resets at the end of the test. -void enableWaitingForDifferentModificationTimes() { +/// +/// IMPORTANT NOTE about "polling" watchers and tests: +/// +/// The polling watchers can notice changes if the "last modified" time of a +/// file changed or if its size changed. +/// +/// Most tests that make changes also change the size of a file, so they do not +/// have to care about whether the timing works out for the modification time to +/// be different. +/// +/// Tests that intentionally write to a file _without_ changing its contents +/// must also call [sleepUntilNewModificationTime] if testing a polling +/// watcher. That ensures that the write causes a new modification time. +/// +/// At the time of writing in 2025, Windows "last modified" time is much coarser +/// than other platforms, at 1s. +void enableSleepUntilNewModificationTime() { if (_waitForModificationTimesDirectory != null) return; _waitForModificationTimesDirectory = Directory.systemTemp.createTempSync('dart_test_'); @@ -76,10 +78,12 @@ void enableWaitingForDifferentModificationTimes() { }); } -/// If [enableWaitingForDifferentModificationTimes] was called, sleeps until a -/// modified file has a new modified timestamp. -void _maybeWaitForDifferentModificationTime() { - if (_waitForModificationTimesDirectory == null) return; +/// Sleeps the until a new modification time is available from the platform. +/// +/// This ensures that the next write can be detected by polling watches based on +/// its "last modified" time, whatever the contents. +void sleepUntilNewModificationTime() { + _waitForModificationTimesDirectory ??= Directory.systemTemp.createTempSync(); var file = File(p.join(_waitForModificationTimesDirectory!.path, 'file')); if (file.existsSync()) file.deleteSync(); file.createSync(); @@ -95,47 +99,6 @@ void _maybeWaitForDifferentModificationTime() { } } -/// Enables mock modification times so that all writes set a different -/// modification time. -/// -/// This will allow polling watchers to notice all writes. -/// -/// Resets at the end of the test. -void enableMockModificationTimes() { - _mockFileModificationTimes = {}; - mockGetModificationTime((path) { - // Resolve symbolic links before looking up mtime to match documented - // behavior of `FileSystemEntity.stat`. - final link = Link(path); - if (link.existsSync()) { - path = link.resolveSymbolicLinksSync(); - } - // Also resolve symbolic links in the enclosing directory. - final file = File(path); - if (file.existsSync()) { - path = file.resolveSymbolicLinksSync(); - } - - var normalized = p.normalize(p.relative(path, from: d.sandbox)); - - // Make sure we got a path in the sandbox. - if (!p.isRelative(normalized) || normalized.startsWith('..')) { - // The polling watcher can poll during test teardown, signal using an - // exception that it will ignore. - throw FileSystemException( - 'Path is not in the sandbox: $path not in ${d.sandbox}', - ); - } - final mockFileModificationTimes = _mockFileModificationTimes!; - var mtime = mockFileModificationTimes[normalized]; - return mtime != null ? DateTime.fromMillisecondsSinceEpoch(mtime) : null; - }); - addTearDown(() { - _mockFileModificationTimes = null; - mockGetModificationTime(null); - }); -} - /// Creates a new [Watcher] that watches a temporary file or directory and /// starts monitoring it for events. /// @@ -244,25 +207,14 @@ Future expectModifyEvent(String path) => Future expectRemoveEvent(String path) => _expect(isWatchEvent(ChangeType.REMOVE, path)); -/// Track a fake timestamp to be used when writing files. This always increases -/// so that files that are deleted and re-created do not have their timestamp -/// set back to a previously used value. -int _nextTimestamp = 1; - /// Writes a file in the sandbox at [path] with [contents]. /// /// If [path] is currently a link it is deleted and a file is written in its /// place. /// /// If [contents] is omitted, creates an empty file. -/// -/// If [updateModified] is `false` and mock modification times are in use, the -/// mock file modification time is not changed. -void writeFile(String path, {String? contents, bool? updateModified}) { - _maybeWaitForDifferentModificationTime(); - +void writeFile(String path, {String? contents}) { contents ??= ''; - updateModified ??= true; var fullPath = p.join(d.sandbox, path); @@ -282,30 +234,15 @@ void writeFile(String path, {String? contents, bool? updateModified}) { file.writeAsStringSync(contents); // Check that `fullPath` now refers to a file, not a link. expect(FileSystemEntity.typeSync(fullPath), FileSystemEntityType.file); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null && updateModified) { - path = p.normalize(path); - - mockFileModificationTimes[path] = _nextTimestamp++; - } } /// Writes a file in the sandbox at [link] pointing to [target]. /// /// [target] is relative to the sandbox, not to [link]. -/// -/// If [updateModified] is `false` and mock modification times are in use, the -/// mock file modification time is not changed. void writeLink({ required String link, required String target, - bool? updateModified, }) { - _maybeWaitForDifferentModificationTime(); - - updateModified ??= true; - var fullPath = p.join(d.sandbox, link); // Create any needed subdirectories. @@ -315,15 +252,6 @@ void writeLink({ } Link(fullPath).createSync(p.join(d.sandbox, target)); - - if (updateModified) { - link = p.normalize(link); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - mockFileModificationTimes[link] = _nextTimestamp++; - } - } } /// Deletes a file in the sandbox at [path]. @@ -332,11 +260,6 @@ void deleteFile(String path) { expect(FileSystemEntity.typeSync(fullPath, followLinks: false), FileSystemEntityType.file); File(fullPath).deleteSync(); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - mockFileModificationTimes.remove(path); - } } /// Deletes a link in the sandbox at [path]. @@ -345,29 +268,14 @@ void deleteLink(String path) { expect(FileSystemEntity.typeSync(fullPath, followLinks: false), FileSystemEntityType.link); Link(fullPath).deleteSync(); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - mockFileModificationTimes.remove(path); - } } /// Renames a file in the sandbox from [from] to [to]. void renameFile(String from, String to) { - _maybeWaitForDifferentModificationTime(); - var absoluteTo = p.join(d.sandbox, to); File(p.join(d.sandbox, from)).renameSync(absoluteTo); expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), FileSystemEntityType.file); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - // Make sure we always use the same separator on Windows. - to = p.normalize(to); - mockFileModificationTimes.update(to, (value) => value + 1, - ifAbsent: () => 1); - } } /// Renames a link in the sandbox from [from] to [to]. @@ -375,20 +283,10 @@ void renameFile(String from, String to) { /// On MacOS and Linux links can also be named with `renameFile`. On Windows, /// however, a link must be renamed with this method. void renameLink(String from, String to) { - _maybeWaitForDifferentModificationTime(); - var absoluteTo = p.join(d.sandbox, to); Link(p.join(d.sandbox, from)).renameSync(absoluteTo); expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), FileSystemEntityType.link); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - // Make sure we always use the same separator on Windows. - to = p.normalize(to); - mockFileModificationTimes.update(to, (value) => value + 1, - ifAbsent: () => 1); - } } /// Creates a directory in the sandbox at [path]. @@ -402,21 +300,6 @@ void renameDir(String from, String to) { Directory(p.join(d.sandbox, from)).renameSync(absoluteTo); expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), FileSystemEntityType.directory); - - final mockFileModificationTimes = _mockFileModificationTimes; - if (mockFileModificationTimes != null) { - // Migrate timestamps for any files in this folder. - final knownFilePaths = mockFileModificationTimes.keys.toList(); - for (final filePath in knownFilePaths) { - if (p.isWithin(from, filePath)) { - final movedPath = - p.normalize(p.join(to, filePath.substring(from.length + 1))); - mockFileModificationTimes[movedPath] = - mockFileModificationTimes[filePath]!; - mockFileModificationTimes.remove(filePath); - } - } - } } /// Deletes a directory in the sandbox at [path].