Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(247)

Unified Diff: packages/watcher/test/utils.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/watcher/test/ready/shared.dart ('k') | packages/web_components/.gitignore » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/watcher/test/utils.dart
diff --git a/packages/watcher/test/utils.dart b/packages/watcher/test/utils.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7dd833231603d1bb2485151ebe608af3fe8ab571
--- /dev/null
+++ b/packages/watcher/test/utils.dart
@@ -0,0 +1,363 @@
+// 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:io';
+
+import 'package:path/path.dart' as p;
+import 'package:scheduled_test/scheduled_stream.dart';
+import 'package:scheduled_test/scheduled_test.dart';
+import 'package:watcher/watcher.dart';
+import 'package:watcher/src/stat.dart';
+import 'package:watcher/src/utils.dart';
+
+// TODO(nweiz): remove this when issue 15042 is fixed.
+import 'package:watcher/src/directory_watcher/mac_os.dart';
+
+/// The path to the temporary sandbox created for each test. All file
+/// operations are implicitly relative to this directory.
+String _sandboxDir;
+
+/// The [Watcher] being used for the current scheduled test.
+Watcher _watcher;
+
+/// 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;
+
+typedef Watcher WatcherFactory(String directory);
+
+/// Sets the function used to create the watcher.
+set watcherFactory(WatcherFactory factory) {
+ _watcherFactory = factory;
+}
+WatcherFactory _watcherFactory;
+
+/// 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 = Directory.systemTemp.createTempSync('watcher_test_');
+ _sandboxDir = dir.path;
+
+ _mockFileModificationTimes = new Map<String, int>();
+ mockGetModificationTime((path) {
+ path = p.normalize(p.relative(path, from: _sandboxDir));
+
+ // Make sure we got a path in the sandbox.
+ assert(p.isRelative(path) && !path.startsWith(".."));
+
+ var mtime = _mockFileModificationTimes[path];
+ return new DateTime.fromMillisecondsSinceEpoch(mtime == null ? 0 : mtime);
+ });
+
+ // Delete the sandbox when done.
+ currentSchedule.onComplete.schedule(() {
+ if (_sandboxDir != null) {
+ // TODO(rnystrom): Issue 19155. The watcher should already be closed when
+ // we clean up the sandbox.
+ if (_watcherEvents != null) {
+ _watcherEvents.close();
+ }
+ new Directory(_sandboxDir).deleteSync(recursive: true);
+ _sandboxDir = null;
+ }
+
+ _mockFileModificationTimes = null;
+ mockGetModificationTime(null);
+ }, "delete sandbox");
+}
+
+/// Creates a new [Watcher] that watches a temporary file or 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.
+///
+/// If [path] is provided, watches a subdirectory in the sandbox with that name.
+Watcher createWatcher({String path, bool waitForReady}) {
+ if (path == null) {
+ path = _sandboxDir;
+ } else {
+ path = p.join(_sandboxDir, path);
+ }
+
+ var watcher = _watcherFactory(path);
+
+ // 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, "wait for watcher to be ready");
+ }
+
+ return watcher;
+}
+
+/// The stream of events from the watcher started with [startWatcher].
+ScheduledStream<WatchEvent> _watcherEvents;
+
+/// Creates a new [Watcher] that watches a temporary file or directory and
+/// starts monitoring it for events.
+///
+/// If [path] is provided, watches a path in the sandbox with that name.
+void startWatcher({String path}) {
+ // We want to wait until we're ready *after* we subscribe to the watcher's
+ // events.
+ _watcher = createWatcher(path: path, waitForReady: false);
+
+ // Schedule [_watcher.events.listen] so that the watcher doesn't start
+ // watching [path] before it exists. Expose [_watcherEvents] immediately so
+ // that it can be accessed synchronously after this.
+ _watcherEvents = new ScheduledStream(futureStream(schedule(() {
+ currentSchedule.onComplete.schedule(() {
+ _watcher = null;
+ if (!_closePending) _watcherEvents.close();
+
+ // If there are already errors, don't add this to the output and make
+ // people think it might be the root cause.
+ if (currentSchedule.errors.isEmpty) {
+ _watcherEvents.expect(isDone);
+ }
+ }, "reset watcher");
+
+ return _watcher.events;
+ }, "create watcher"), broadcast: true));
+
+ schedule(() => _watcher.ready, "wait for watcher to be ready");
+}
+
+/// Whether an event to close [_watcherEvents] has been scheduled.
+bool _closePending = false;
+
+/// Schedule closing the watcher stream after the event queue has been pumped.
+///
+/// This is necessary when events are allowed to occur, but don't have to occur,
+/// at the end of a test. Otherwise, if they don't occur, the test will wait
+/// indefinitely because they might in the future and because the watcher is
+/// normally only closed after the test completes.
+void startClosingEventStream() {
+ schedule(() {
+ _closePending = true;
+ pumpEventQueue().then((_) => _watcherEvents.close()).whenComplete(() {
+ _closePending = false;
+ });
+ }, 'start closing event stream');
+}
+
+/// A list of [StreamMatcher]s that have been collected using
+/// [_collectStreamMatcher].
+List<StreamMatcher> _collectedStreamMatchers;
+
+/// Collects all stream matchers that are registered within [block] into a
+/// single stream matcher.
+///
+/// The returned matcher will match each of the collected matchers in order.
+StreamMatcher _collectStreamMatcher(block()) {
+ var oldStreamMatchers = _collectedStreamMatchers;
+ _collectedStreamMatchers = new List<StreamMatcher>();
+ try {
+ block();
+ return inOrder(_collectedStreamMatchers);
+ } finally {
+ _collectedStreamMatchers = oldStreamMatchers;
+ }
+}
+
+/// Either add [streamMatcher] as an expectation to [_watcherEvents], or collect
+/// it with [_collectStreamMatcher].
+///
+/// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value.
+void _expectOrCollect(streamMatcher) {
+ if (_collectedStreamMatchers != null) {
+ _collectedStreamMatchers.add(new StreamMatcher.wrap(streamMatcher));
+ } else {
+ _watcherEvents.expect(streamMatcher);
+ }
+}
+
+/// Expects that [matchers] will match emitted events in any order.
+///
+/// [matchers] may be [Matcher]s or values, but not [StreamMatcher]s.
+void inAnyOrder(Iterable matchers) {
+ matchers = matchers.toSet();
+ _expectOrCollect(nextValues(matchers.length, unorderedMatches(matchers)));
+}
+
+/// Expects that the expectations established in either [block1] or [block2]
+/// will match the emitted events.
+///
+/// If both blocks match, the one that consumed more events will be used.
+void allowEither(block1(), block2()) {
+ _expectOrCollect(either(
+ _collectStreamMatcher(block1), _collectStreamMatcher(block2)));
+}
+
+/// Allows the expectations established in [block] to match the emitted events.
+///
+/// If the expectations in [block] don't match, no error will be raised and no
+/// events will be consumed. If this is used at the end of a test,
+/// [startClosingEventStream] should be called before it.
+void allowEvents(block()) {
+ _expectOrCollect(allow(_collectStreamMatcher(block)));
+}
+
+/// Returns a matcher that matches a [WatchEvent] with the given [type] and
+/// [path].
+Matcher isWatchEvent(ChangeType type, String path) {
+ return predicate((e) {
+ return e is WatchEvent && e.type == type &&
+ e.path == p.join(_sandboxDir, p.normalize(path));
+ }, "is $type $path");
+}
+
+/// Returns a [Matcher] that matches a [WatchEvent] for an add event for [path].
+Matcher isAddEvent(String path) => isWatchEvent(ChangeType.ADD, path);
+
+/// Returns a [Matcher] that matches a [WatchEvent] for a modification event for
+/// [path].
+Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path);
+
+/// Returns a [Matcher] that matches a [WatchEvent] for a removal event for
+/// [path].
+Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path);
+
+/// Expects that the next event emitted will be for an add event for [path].
+void expectAddEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.ADD, path));
+
+/// Expects that the next event emitted will be for a modification event for
+/// [path].
+void expectModifyEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
+
+/// Expects that the next event emitted will be for a removal event for [path].
+void expectRemoveEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path));
+
+/// Consumes an add event for [path] if one is emitted at this point in the
+/// schedule, but doesn't throw an error if it isn't.
+///
+/// If this is used at the end of a test, [startClosingEventStream] should be
+/// called before it.
+void allowAddEvent(String path) =>
+ _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path)));
+
+/// Consumes a modification event for [path] if one is emitted at this point in
+/// the schedule, but doesn't throw an error if it isn't.
+///
+/// If this is used at the end of a test, [startClosingEventStream] should be
+/// called before it.
+void allowModifyEvent(String path) =>
+ _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path)));
+
+/// Consumes a removal event for [path] if one is emitted at this point in the
+/// schedule, but doesn't throw an error if it isn't.
+///
+/// If this is used at the end of a test, [startClosingEventStream] should be
+/// called before it.
+void allowRemoveEvent(String path) =>
+ _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, 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 = p.join(_sandboxDir, path);
+
+ // Create any needed subdirectories.
+ var dir = new Directory(p.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) {
+ // Make sure we always use the same separator on Windows.
+ path = p.normalize(path);
+
+ var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0);
+ _mockFileModificationTimes[path]++;
+ }
+ }, "write file $path");
+}
+
+/// Schedules deleting a file in the sandbox at [path].
+void deleteFile(String path) {
+ schedule(() {
+ new File(p.join(_sandboxDir, path)).deleteSync();
+ }, "delete file $path");
+}
+
+/// 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(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to));
+
+ // Make sure we always use the same separator on Windows.
+ to = p.normalize(to);
+
+ // Manually update the mock modification time for the file.
+ var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0);
+ _mockFileModificationTimes[to]++;
+ }, "rename file $from to $to");
+}
+
+/// Schedules creating a directory in the sandbox at [path].
+void createDir(String path) {
+ schedule(() {
+ new Directory(p.join(_sandboxDir, path)).createSync();
+ }, "create directory $path");
+}
+
+/// Schedules renaming a directory in the sandbox from [from] to [to].
+void renameDir(String from, String to) {
+ schedule(() {
+ new Directory(p.join(_sandboxDir, from))
+ .renameSync(p.join(_sandboxDir, to));
+ }, "rename directory $from to $to");
+}
+
+/// Schedules deleting a directory in the sandbox at [path].
+void deleteDir(String path) {
+ schedule(() {
+ new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true);
+ }, "delete directory $path");
+}
+
+/// Runs [callback] with every permutation of non-negative [i], [j], and [k]
+/// less than [limit].
+///
+/// Returns a set of all values returns by [callback].
+///
+/// [limit] defaults to 3.
+Set withPermutations(callback(int i, int j, int k), {int limit}) {
+ if (limit == null) limit = 3;
+ var results = new Set();
+ for (var i = 0; i < limit; i++) {
+ for (var j = 0; j < limit; j++) {
+ for (var k = 0; k < limit; k++) {
+ results.add(callback(i, j, k));
+ }
+ }
+ }
+ return results;
+}
« no previous file with comments | « packages/watcher/test/ready/shared.dart ('k') | packages/web_components/.gitignore » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698