123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- /** @babel */
- import path from 'path';
- import { Emitter } from 'event-kit';
- import { NativeWatcherRegistry } from '../src/native-watcher-registry';
- function findRootDirectory() {
- let current = process.cwd();
- while (true) {
- let next = path.resolve(current, '..');
- if (next === current) {
- return next;
- } else {
- current = next;
- }
- }
- }
- const ROOT = findRootDirectory();
- function absolute(...parts) {
- const candidate = path.join(...parts);
- return path.isAbsolute(candidate) ? candidate : path.join(ROOT, candidate);
- }
- function parts(fullPath) {
- return fullPath.split(path.sep).filter(part => part.length > 0);
- }
- class MockWatcher {
- constructor(normalizedPath) {
- this.normalizedPath = normalizedPath;
- this.native = null;
- }
- getNormalizedPathPromise() {
- return Promise.resolve(this.normalizedPath);
- }
- attachToNative(native, nativePath) {
- if (this.normalizedPath.startsWith(nativePath)) {
- if (this.native) {
- this.native.attached = this.native.attached.filter(
- each => each !== this
- );
- }
- this.native = native;
- this.native.attached.push(this);
- }
- }
- }
- class MockNative {
- constructor(name) {
- this.name = name;
- this.attached = [];
- this.disposed = false;
- this.stopped = false;
- this.emitter = new Emitter();
- }
- reattachTo(newNative, nativePath) {
- for (const watcher of this.attached) {
- watcher.attachToNative(newNative, nativePath);
- }
- }
- onWillStop(callback) {
- return this.emitter.on('will-stop', callback);
- }
- dispose() {
- this.disposed = true;
- }
- stop() {
- this.stopped = true;
- this.emitter.emit('will-stop');
- }
- }
- describe('NativeWatcherRegistry', function() {
- let createNative, registry;
- beforeEach(function() {
- registry = new NativeWatcherRegistry(normalizedPath =>
- createNative(normalizedPath)
- );
- });
- it('attaches a Watcher to a newly created NativeWatcher for a new directory', async function() {
- const watcher = new MockWatcher(absolute('some', 'path'));
- const NATIVE = new MockNative('created');
- createNative = () => NATIVE;
- await registry.attach(watcher);
- expect(watcher.native).toBe(NATIVE);
- });
- it('reuses an existing NativeWatcher on the same directory', async function() {
- this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD();
- const EXISTING = new MockNative('existing');
- const existingPath = absolute('existing', 'path');
- let firstTime = true;
- createNative = () => {
- if (firstTime) {
- firstTime = false;
- return EXISTING;
- }
- return new MockNative('nope');
- };
- await registry.attach(new MockWatcher(existingPath));
- const watcher = new MockWatcher(existingPath);
- await registry.attach(watcher);
- expect(watcher.native).toBe(EXISTING);
- });
- it('attaches to an existing NativeWatcher on a parent directory', async function() {
- const EXISTING = new MockNative('existing');
- const parentDir = absolute('existing', 'path');
- const subDir = path.join(parentDir, 'sub', 'directory');
- let firstTime = true;
- createNative = () => {
- if (firstTime) {
- firstTime = false;
- return EXISTING;
- }
- return new MockNative('nope');
- };
- await registry.attach(new MockWatcher(parentDir));
- const watcher = new MockWatcher(subDir);
- await registry.attach(watcher);
- expect(watcher.native).toBe(EXISTING);
- });
- it('adopts Watchers from NativeWatchers on child directories', async function() {
- const parentDir = absolute('existing', 'path');
- const childDir0 = path.join(parentDir, 'child', 'directory', 'zero');
- const childDir1 = path.join(parentDir, 'child', 'directory', 'one');
- const otherDir = absolute('another', 'path');
- const CHILD0 = new MockNative('existing0');
- const CHILD1 = new MockNative('existing1');
- const OTHER = new MockNative('existing2');
- const PARENT = new MockNative('parent');
- createNative = dir => {
- if (dir === childDir0) {
- return CHILD0;
- } else if (dir === childDir1) {
- return CHILD1;
- } else if (dir === otherDir) {
- return OTHER;
- } else if (dir === parentDir) {
- return PARENT;
- } else {
- throw new Error(`Unexpected path: ${dir}`);
- }
- };
- const watcher0 = new MockWatcher(childDir0);
- await registry.attach(watcher0);
- const watcher1 = new MockWatcher(childDir1);
- await registry.attach(watcher1);
- const watcher2 = new MockWatcher(otherDir);
- await registry.attach(watcher2);
- expect(watcher0.native).toBe(CHILD0);
- expect(watcher1.native).toBe(CHILD1);
- expect(watcher2.native).toBe(OTHER);
- // Consolidate all three watchers beneath the same native watcher on the parent directory
- const watcher = new MockWatcher(parentDir);
- await registry.attach(watcher);
- expect(watcher.native).toBe(PARENT);
- expect(watcher0.native).toBe(PARENT);
- expect(CHILD0.stopped).toBe(true);
- expect(CHILD0.disposed).toBe(true);
- expect(watcher1.native).toBe(PARENT);
- expect(CHILD1.stopped).toBe(true);
- expect(CHILD1.disposed).toBe(true);
- expect(watcher2.native).toBe(OTHER);
- expect(OTHER.stopped).toBe(false);
- expect(OTHER.disposed).toBe(false);
- });
- describe('removing NativeWatchers', function() {
- it('happens when they stop', async function() {
- const STOPPED = new MockNative('stopped');
- const RUNNING = new MockNative('running');
- const stoppedPath = absolute('watcher', 'that', 'will', 'be', 'stopped');
- const stoppedPathParts = stoppedPath
- .split(path.sep)
- .filter(part => part.length > 0);
- const runningPath = absolute(
- 'watcher',
- 'that',
- 'will',
- 'continue',
- 'to',
- 'exist'
- );
- const runningPathParts = runningPath
- .split(path.sep)
- .filter(part => part.length > 0);
- createNative = dir => {
- if (dir === stoppedPath) {
- return STOPPED;
- } else if (dir === runningPath) {
- return RUNNING;
- } else {
- throw new Error(`Unexpected path: ${dir}`);
- }
- };
- const stoppedWatcher = new MockWatcher(stoppedPath);
- await registry.attach(stoppedWatcher);
- const runningWatcher = new MockWatcher(runningPath);
- await registry.attach(runningWatcher);
- STOPPED.stop();
- const runningNode = registry.tree.root.lookup(runningPathParts).when({
- parent: node => node,
- missing: () => false,
- children: () => false
- });
- expect(runningNode).toBeTruthy();
- expect(runningNode.getNativeWatcher()).toBe(RUNNING);
- const stoppedNode = registry.tree.root.lookup(stoppedPathParts).when({
- parent: () => false,
- missing: () => true,
- children: () => false
- });
- expect(stoppedNode).toBe(true);
- });
- it('reassigns new child watchers when a parent watcher is stopped', async function() {
- const CHILD0 = new MockNative('child0');
- const CHILD1 = new MockNative('child1');
- const PARENT = new MockNative('parent');
- const parentDir = absolute('parent');
- const childDir0 = path.join(parentDir, 'child0');
- const childDir1 = path.join(parentDir, 'child1');
- createNative = dir => {
- if (dir === parentDir) {
- return PARENT;
- } else if (dir === childDir0) {
- return CHILD0;
- } else if (dir === childDir1) {
- return CHILD1;
- } else {
- throw new Error(`Unexpected directory ${dir}`);
- }
- };
- const parentWatcher = new MockWatcher(parentDir);
- const childWatcher0 = new MockWatcher(childDir0);
- const childWatcher1 = new MockWatcher(childDir1);
- await registry.attach(parentWatcher);
- await Promise.all([
- registry.attach(childWatcher0),
- registry.attach(childWatcher1)
- ]);
- // All three watchers should share the parent watcher's native watcher.
- expect(parentWatcher.native).toBe(PARENT);
- expect(childWatcher0.native).toBe(PARENT);
- expect(childWatcher1.native).toBe(PARENT);
- // Stopping the parent should detach and recreate the child watchers.
- PARENT.stop();
- expect(childWatcher0.native).toBe(CHILD0);
- expect(childWatcher1.native).toBe(CHILD1);
- expect(
- registry.tree.root.lookup(parts(parentDir)).when({
- parent: () => false,
- missing: () => false,
- children: () => true
- })
- ).toBe(true);
- expect(
- registry.tree.root.lookup(parts(childDir0)).when({
- parent: () => true,
- missing: () => false,
- children: () => false
- })
- ).toBe(true);
- expect(
- registry.tree.root.lookup(parts(childDir1)).when({
- parent: () => true,
- missing: () => false,
- children: () => false
- })
- ).toBe(true);
- });
- it('consolidates children when splitting a parent watcher', async function() {
- const CHILD0 = new MockNative('child0');
- const PARENT = new MockNative('parent');
- const parentDir = absolute('parent');
- const childDir0 = path.join(parentDir, 'child0');
- const childDir1 = path.join(parentDir, 'child0', 'child1');
- createNative = dir => {
- if (dir === parentDir) {
- return PARENT;
- } else if (dir === childDir0) {
- return CHILD0;
- } else {
- throw new Error(`Unexpected directory ${dir}`);
- }
- };
- const parentWatcher = new MockWatcher(parentDir);
- const childWatcher0 = new MockWatcher(childDir0);
- const childWatcher1 = new MockWatcher(childDir1);
- await registry.attach(parentWatcher);
- await Promise.all([
- registry.attach(childWatcher0),
- registry.attach(childWatcher1)
- ]);
- // All three watchers should share the parent watcher's native watcher.
- expect(parentWatcher.native).toBe(PARENT);
- expect(childWatcher0.native).toBe(PARENT);
- expect(childWatcher1.native).toBe(PARENT);
- // Stopping the parent should detach and create the child watchers. Both child watchers should
- // share the same native watcher.
- PARENT.stop();
- expect(childWatcher0.native).toBe(CHILD0);
- expect(childWatcher1.native).toBe(CHILD0);
- expect(
- registry.tree.root.lookup(parts(parentDir)).when({
- parent: () => false,
- missing: () => false,
- children: () => true
- })
- ).toBe(true);
- expect(
- registry.tree.root.lookup(parts(childDir0)).when({
- parent: () => true,
- missing: () => false,
- children: () => false
- })
- ).toBe(true);
- expect(
- registry.tree.root.lookup(parts(childDir1)).when({
- parent: () => true,
- missing: () => false,
- children: () => false
- })
- ).toBe(true);
- });
- });
- });
|