blob: 23817f40448bad49791885aea72984752abceb8a [file] [log] [blame]
// Copyright (c) 2021, 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:async';
import 'dart:isolate';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer/src/workspace/bazel.dart';
import 'package:analyzer/src/workspace/bazel_watcher.dart';
import 'package:async/async.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:watcher/watcher.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(BazelWatcherTest);
});
}
@reflectiveTest
class BazelWatcherTest with ResourceProviderMixin {
late final BazelWorkspace workspace;
void test_bazelFileWatcher() async {
_addResources([
'/workspace/WORKSPACE',
]);
var candidates = [
convertPath('/workspace/bazel-bin/my/module/test1.dart'),
convertPath('/workspace/bazel-genfiles/my/module/test1.dart'),
];
var watcher = BazelFilePoller(resourceProvider, candidates);
// First do some tests with the first candidate path.
_addResources([candidates[0]]);
var event = watcher.poll()!;
expect(event.type, ChangeType.ADD);
expect(event.path, candidates[0]);
modifyFile(candidates[0], 'const foo = 42;');
event = watcher.poll()!;
expect(event.type, ChangeType.MODIFY);
expect(event.path, candidates[0]);
_deleteResources([candidates[0]]);
event = watcher.poll()!;
expect(event.type, ChangeType.REMOVE);
expect(event.path, candidates[0]);
// Now check that if we add the *second* candidate, we'll get the
// notification for it.
_addResources([candidates[1]]);
event = watcher.poll()!;
expect(event.type, ChangeType.ADD);
expect(event.path, candidates[1]);
// Next poll should be `null` since there were no changes.
expect(watcher.poll(), isNull);
}
void test_bazelFileWatcherIsolate() async {
_addResources([
'/workspace/WORKSPACE',
]);
var candidates1 = [
convertPath('/workspace/bazel-bin/my/module/test1.dart'),
convertPath('/workspace/bazel-genfiles/my/module/test1.dart'),
];
var candidates2 = [
convertPath('/workspace/bazel-bin/my/module/test2.dart'),
convertPath('/workspace/bazel-genfiles/my/module/test2.dart'),
];
var trigger = _MockPollTrigger();
var recPort = ReceivePort();
// Note that we provide below a dummy `ReceivePort` that will *not* be used.
// We'll directly call `handleRequest` to avoid any problems with various
// interleavings of async functions.
var dummyRecPort = ReceivePort();
var watcher = BazelFileWatcherIsolate(
dummyRecPort, recPort.sendPort, resourceProvider,
pollTriggerFactory: (_) => trigger)
..start();
var queue = StreamQueue(recPort);
await queue.next as BazelWatcherIsolateStarted;
watcher.handleRequest(BazelWatcherStartWatching(
convertPath('/workspace'),
BazelSearchInfo(
convertPath('/workspace/my/module/test1.dart'), candidates1)));
watcher.handleRequest(BazelWatcherStartWatching(
convertPath('/workspace'),
BazelSearchInfo(
convertPath('/workspace/my/module/test2.dart'), candidates2)));
// First do some tests with the first candidate path.
_addResources([candidates1[0]]);
trigger.controller.add('');
var events = (await queue.next as BazelWatcherEvents).events;
expect(events, hasLength(1));
expect(events[0].path, candidates1[0]);
expect(events[0].type, ChangeType.ADD);
// Now let's take a look at the second file.
_addResources([candidates2[1]]);
trigger.controller.add('');
events = (await queue.next as BazelWatcherEvents).events;
expect(events, hasLength(1));
expect(events[0].path, candidates2[1]);
expect(events[0].type, ChangeType.ADD);
expect(watcher.numWatchedFiles(), 2);
watcher.handleRequest(BazelWatcherStopWatching(convertPath('/workspace'),
convertPath('/workspace/my/module/test1.dart')));
expect(watcher.numWatchedFiles(), 1);
watcher.handleRequest(BazelWatcherStopWatching(convertPath('/workspace'),
convertPath('/workspace/my/module/test2.dart')));
expect(watcher.numWatchedFiles(), 0);
watcher.handleRequest(BazelWatcherShutdownIsolate());
await watcher.hasFinished;
// We need to do this manually, since it's the callers responsibility to
// close this port (the one owned by `watcher` should've been closed when
// handling the "shutdown" request).
recPort.close();
}
void test_bazelFileWatcherIsolate_multipleWorkspaces() async {
_addResources([
'/workspace1/WORKSPACE',
'/workspace2/WORKSPACE',
]);
var candidates1 = [
convertPath('/workspace1/bazel-bin/my/module/test1.dart'),
convertPath('/workspace1/bazel-genfiles/my/module/test1.dart'),
];
var candidates2 = [
convertPath('/workspace2/bazel-bin/my/module/test2.dart'),
convertPath('/workspace2/bazel-genfiles/my/module/test2.dart'),
];
_MockPollTrigger? trigger1;
_MockPollTrigger? trigger2;
var triggerFactory = (String workspace) {
if (workspace == convertPath('/workspace1')) {
trigger1 = _MockPollTrigger();
return trigger1!;
} else if (workspace == convertPath('/workspace2')) {
trigger2 = _MockPollTrigger();
return trigger2!;
} else {
throw ArgumentError('Got unexpected workspace: `$workspace`');
}
};
var recPort = ReceivePort();
// Note that we provide below a dummy `ReceivePort` that will *not* be used.
// We'll directly call `handleRequest` to avoid any problems with various
// interleavings of async functions.
var dummyRecPort = ReceivePort();
var watcher = BazelFileWatcherIsolate(
dummyRecPort, recPort.sendPort, resourceProvider,
pollTriggerFactory: triggerFactory)
..start();
var queue = StreamQueue(recPort);
await queue.next as BazelWatcherIsolateStarted;
watcher.handleRequest(BazelWatcherStartWatching(
convertPath('/workspace1'),
BazelSearchInfo(
convertPath('/workspace1/my/module/test1.dart'), candidates1)));
watcher.handleRequest(BazelWatcherStartWatching(
convertPath('/workspace2'),
BazelSearchInfo(
convertPath('/workspace2/my/module/test2.dart'), candidates2)));
// First do some tests with the first candidate path.
_addResources([candidates1[0]]);
trigger1!.controller.add('');
var events = (await queue.next as BazelWatcherEvents).events;
expect(events, hasLength(1));
expect(events[0].path, candidates1[0]);
expect(events[0].type, ChangeType.ADD);
// Now let's take a look at the second file.
_addResources([candidates2[1]]);
trigger2!.controller.add('');
events = (await queue.next as BazelWatcherEvents).events;
expect(events, hasLength(1));
expect(events[0].path, candidates2[1]);
expect(events[0].type, ChangeType.ADD);
expect(watcher.numWatchedFiles(), 2);
watcher.handleRequest(BazelWatcherStopWatching(convertPath('/workspace1'),
convertPath('/workspace1/my/module/test1.dart')));
expect(watcher.numWatchedFiles(), 1);
watcher.handleRequest(BazelWatcherStopWatching(convertPath('/workspace2'),
convertPath('/workspace2/my/module/test2.dart')));
expect(watcher.numWatchedFiles(), 0);
watcher.handleRequest(BazelWatcherShutdownIsolate());
await watcher.hasFinished;
// We need to do this manually, since it's the callers responsibility to
// close this port (the one owned by `watcher` should've been closed when
// handling the "shutdown" request).
recPort.close();
}
void test_bazelFileWatcherWithFolder() async {
_addResources([
'/workspace/WORKSPACE',
]);
// The `_addResources`/`_deleteResources` functions recognize a folder by a
// trailing `/`, but everywhere else we need to use normalized paths.
var addFolder = (path) => _addResources(['$path/']);
var deleteFolder = (path) => _deleteResources(['$path/']);
var candidates = [
convertPath('/workspace/bazel-out'),
convertPath('/workspace/blaze-out'),
];
var watcher = BazelFilePoller(resourceProvider, candidates);
// First do some tests with the first candidate path.
addFolder(candidates[0]);
var event = watcher.poll()!;
expect(event.type, ChangeType.ADD);
expect(event.path, candidates[0]);
deleteFolder(candidates[0]);
event = watcher.poll()!;
expect(event.type, ChangeType.REMOVE);
expect(event.path, candidates[0]);
// Now check that if we add the *second* candidate, we'll get the
// notification for it.
addFolder(candidates[1]);
event = watcher.poll()!;
expect(event.type, ChangeType.ADD);
expect(event.path, candidates[1]);
// Next poll should be `null` since there were no changes.
expect(watcher.poll(), isNull);
}
/// Create new files and directories from [paths].
void _addResources(List<String> paths) {
for (String path in paths) {
if (path.endsWith('/')) {
newFolder(path.substring(0, path.length - 1));
} else {
newFile2(path, '');
}
}
}
/// Create new files and directories from [paths].
void _deleteResources(List<String> paths) {
for (String path in paths) {
if (path.endsWith('/')) {
deleteFolder(path.substring(0, path.length - 1));
} else {
deleteFile(path);
}
}
}
}
class _MockPollTrigger implements PollTrigger {
final controller = StreamController<Object>();
@override
Stream<Object> get stream => controller.stream;
@override
void cancel() {
return;
}
}