| Index: pkg/watcher/test/utils.dart
|
| diff --git a/pkg/watcher/test/utils.dart b/pkg/watcher/test/utils.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..387b4ad204590dc635d1f98b7d45e500046d10f5
|
| --- /dev/null
|
| +++ b/pkg/watcher/test/utils.dart
|
| @@ -0,0 +1,186 @@
|
| +// Copyright (c) 2012, 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.
|
| +
|
| +library watcher.test.utils;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:io';
|
| +
|
| +import 'package:path/path.dart';
|
| +import 'package:scheduled_test/scheduled_test.dart';
|
| +import 'package:unittest/compact_vm_config.dart';
|
| +import 'package:watcher/watcher.dart';
|
| +import 'package:watcher/src/stat.dart';
|
| +
|
| +/// The path to the temporary sandbox created for each test. All file
|
| +/// operations are implicitly relative to this directory.
|
| +String _sandboxDir;
|
| +
|
| +/// The [DirectoryWatcher] being used for the current scheduled test.
|
| +DirectoryWatcher _watcher;
|
| +
|
| +/// The index in [_watcher]'s event stream for the next event. When event
|
| +/// expectations are set using [expectEvent] (et. al.), they use this to
|
| +/// expect a series of events in order.
|
| +var _nextEvent = 0;
|
| +
|
| +/// 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<String, int> _mockFileModificationTimes;
|
| +
|
| +void initConfig() {
|
| + useCompactVMConfiguration();
|
| +}
|
| +
|
| +/// Creates the sandbox directory the other functions in this library use and
|
| +/// ensures it's deleted when the test ends.
|
| +///
|
| +/// This should usually be called by [setUp].
|
| +void createSandbox() {
|
| + var dir = new Directory("").createTempSync();
|
| + _sandboxDir = dir.path;
|
| +
|
| + _mockFileModificationTimes = new Map<String, int>();
|
| + mockGetModificationTime((path) {
|
| + path = relative(path, from: _sandboxDir);
|
| +
|
| + // Make sure we got a path in the sandbox.
|
| + assert(isRelative(path) && !path.startsWith(".."));
|
| +
|
| + return new DateTime.fromMillisecondsSinceEpoch(
|
| + _mockFileModificationTimes[path]);
|
| + });
|
| +
|
| + // Delete the sandbox when done.
|
| + currentSchedule.onComplete.schedule(() {
|
| + if (_sandboxDir != null) {
|
| + new Directory(_sandboxDir).deleteSync(recursive: true);
|
| + _sandboxDir = null;
|
| + }
|
| +
|
| + _mockFileModificationTimes = null;
|
| + mockGetModificationTime(null);
|
| + }, "delete sandbox");
|
| +}
|
| +
|
| +/// Creates a new [DirectoryWatcher] that watches a temporary directory.
|
| +///
|
| +/// Normally, this will pause the schedule until the watcher is done scanning
|
| +/// and is polling for changes. If you pass `false` for [waitForReady], it will
|
| +/// not schedule this delay.
|
| +DirectoryWatcher createWatcher({bool waitForReady}) {
|
| + _watcher = new DirectoryWatcher(_sandboxDir);
|
| +
|
| + // Wait until the scan is finished so that we don't miss changes to files
|
| + // that could occur before the scan completes.
|
| + if (waitForReady != false) {
|
| + schedule(() => _watcher.ready);
|
| + }
|
| +
|
| + currentSchedule.onComplete.schedule(() {
|
| + _nextEvent = 0;
|
| + _watcher = null;
|
| + }, "reset watcher");
|
| +
|
| + return _watcher;
|
| +}
|
| +
|
| +void expectEvent(ChangeType type, String path) {
|
| + // Immediately create the future. This ensures we don't register too late and
|
| + // drop the event before we receive it.
|
| + var future = _watcher.events.elementAt(_nextEvent++).then((event) {
|
| + expect(event, new _ChangeMatcher(type, path));
|
| + });
|
| +
|
| + // Make sure the schedule is watching it in case it fails.
|
| + currentSchedule.wrapFuture(future);
|
| +
|
| + // Schedule it so that later file modifications don't occur until after this
|
| + // event is received.
|
| + schedule(() => future);
|
| +}
|
| +
|
| +void expectAddEvent(String path) {
|
| + expectEvent(ChangeType.ADD, join(_sandboxDir, path));
|
| +}
|
| +
|
| +void expectModifyEvent(String path) {
|
| + expectEvent(ChangeType.MODIFY, join(_sandboxDir, path));
|
| +}
|
| +
|
| +void expectRemoveEvent(String path) {
|
| + expectEvent(ChangeType.REMOVE, join(_sandboxDir, path));
|
| +}
|
| +
|
| +/// Schedules writing a file in the sandbox at [path] with [contents].
|
| +///
|
| +/// If [contents] is omitted, creates an empty file. If [updatedModified] is
|
| +/// `false`, the mock file modification time is not changed.
|
| +void writeFile(String path, {String contents, bool updateModified}) {
|
| + if (contents == null) contents = "";
|
| + if (updateModified == null) updateModified = true;
|
| +
|
| + schedule(() {
|
| + var fullPath = join(_sandboxDir, path);
|
| +
|
| + // Create any needed subdirectories.
|
| + var dir = new Directory(dirname(fullPath));
|
| + if (!dir.existsSync()) {
|
| + dir.createSync(recursive: true);
|
| + }
|
| +
|
| + new File(fullPath).writeAsStringSync(contents);
|
| +
|
| + // Manually update the mock modification time for the file.
|
| + if (updateModified) {
|
| + var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0);
|
| + _mockFileModificationTimes[path]++;
|
| + }
|
| + });
|
| +}
|
| +
|
| +/// Schedules deleting a file in the sandbox at [path].
|
| +void deleteFile(String path) {
|
| + schedule(() {
|
| + new File(join(_sandboxDir, path)).deleteSync();
|
| + });
|
| +}
|
| +
|
| +/// Schedules renaming a file in the sandbox from [from] to [to].
|
| +///
|
| +/// If [contents] is omitted, creates an empty file.
|
| +void renameFile(String from, String to) {
|
| + schedule(() {
|
| + new File(join(_sandboxDir, from)).renameSync(join(_sandboxDir, to));
|
| +
|
| + // Manually update the mock modification time for the file.
|
| + var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0);
|
| + _mockFileModificationTimes[to]++;
|
| + });
|
| +}
|
| +
|
| +/// A [Matcher] for [WatchEvent]s.
|
| +class _ChangeMatcher extends BaseMatcher {
|
| + /// The expected change.
|
| + final ChangeType type;
|
| +
|
| + /// The expected path.
|
| + final String path;
|
| +
|
| + _ChangeMatcher(this.type, this.path);
|
| +
|
| + Description describe(Description description) {
|
| + description.add("$type $path");
|
| + }
|
| +
|
| + bool matches(item, Map matchState) =>
|
| + item is WatchEvent && item.type == type && item.path == path;
|
| +}
|
|
|