pulsar/spec/native-watcher-registry-spec.js

390 lines
11 KiB
JavaScript
Raw Normal View History

/** @babel */
2019-05-31 19:33:56 +03:00
import path from 'path';
import { Emitter } from 'event-kit';
2017-06-21 18:08:29 +03:00
2019-05-31 19:33:56 +03:00
import { NativeWatcherRegistry } from '../src/native-watcher-registry';
2019-05-31 19:33:56 +03:00
function findRootDirectory() {
let current = process.cwd();
while (true) {
2019-05-31 19:33:56 +03:00
let next = path.resolve(current, '..');
if (next === current) {
2019-05-31 19:33:56 +03:00
return next;
} else {
2019-05-31 19:33:56 +03:00
current = next;
}
}
}
2019-05-31 19:33:56 +03:00
const ROOT = findRootDirectory();
2019-05-31 19:33:56 +03:00
function absolute(...parts) {
const candidate = path.join(...parts);
return path.isAbsolute(candidate) ? candidate : path.join(ROOT, candidate);
}
2019-05-31 19:33:56 +03:00
function parts(fullPath) {
return fullPath.split(path.sep).filter(part => part.length > 0);
}
class MockWatcher {
2019-05-31 19:33:56 +03:00
constructor(normalizedPath) {
this.normalizedPath = normalizedPath;
this.native = null;
}
2019-05-31 19:33:56 +03:00
getNormalizedPathPromise() {
return Promise.resolve(this.normalizedPath);
}
2019-05-31 19:33:56 +03:00
attachToNative(native, nativePath) {
if (this.normalizedPath.startsWith(nativePath)) {
if (this.native) {
2019-02-22 10:55:17 +03:00
this.native.attached = this.native.attached.filter(
each => each !== this
2019-05-31 19:33:56 +03:00
);
}
2019-05-31 19:33:56 +03:00
this.native = native;
this.native.attached.push(this);
}
}
}
class MockNative {
2019-05-31 19:33:56 +03:00
constructor(name) {
this.name = name;
this.attached = [];
this.disposed = false;
this.stopped = false;
2017-06-21 18:08:29 +03:00
2019-05-31 19:33:56 +03:00
this.emitter = new Emitter();
}
2019-05-31 19:33:56 +03:00
reattachTo(newNative, nativePath) {
for (const watcher of this.attached) {
2019-05-31 19:33:56 +03:00
watcher.attachToNative(newNative, nativePath);
}
}
2019-05-31 19:33:56 +03:00
onWillStop(callback) {
return this.emitter.on('will-stop', callback);
2017-06-21 18:08:29 +03:00
}
2019-05-31 19:33:56 +03:00
dispose() {
this.disposed = true;
}
2019-05-31 19:33:56 +03:00
stop() {
this.stopped = true;
this.emitter.emit('will-stop');
}
}
2019-05-31 19:33:56 +03:00
describe('NativeWatcherRegistry', function() {
let createNative, registry;
2019-05-31 19:33:56 +03:00
beforeEach(function() {
2019-02-22 10:55:17 +03:00
registry = new NativeWatcherRegistry(normalizedPath =>
createNative(normalizedPath)
2019-05-31 19:33:56 +03:00
);
});
2019-05-31 19:33:56 +03:00
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;
2019-05-31 19:33:56 +03:00
await registry.attach(watcher);
2019-05-31 19:33:56 +03:00
expect(watcher.native).toBe(NATIVE);
});
2019-05-31 19:33:56 +03:00
it('reuses an existing NativeWatcher on the same directory', async function() {
this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD();
2019-04-10 23:08:02 +03:00
2019-05-31 19:33:56 +03:00
const EXISTING = new MockNative('existing');
const existingPath = absolute('existing', 'path');
let firstTime = true;
createNative = () => {
if (firstTime) {
2019-05-31 19:33:56 +03:00
firstTime = false;
return EXISTING;
}
2019-05-31 19:33:56 +03:00
return new MockNative('nope');
};
await registry.attach(new MockWatcher(existingPath));
2019-05-31 19:33:56 +03:00
const watcher = new MockWatcher(existingPath);
await registry.attach(watcher);
2019-05-31 19:33:56 +03:00
expect(watcher.native).toBe(EXISTING);
});
2019-05-31 19:33:56 +03:00
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) {
2019-05-31 19:33:56 +03:00
firstTime = false;
return EXISTING;
}
2019-05-31 19:33:56 +03:00
return new MockNative('nope');
};
await registry.attach(new MockWatcher(parentDir));
2019-05-31 19:33:56 +03:00
const watcher = new MockWatcher(subDir);
await registry.attach(watcher);
2019-05-31 19:33:56 +03:00
expect(watcher.native).toBe(EXISTING);
});
2019-05-31 19:33:56 +03:00
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');
2019-05-31 19:33:56 +03:00
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) {
2019-05-31 19:33:56 +03:00
return CHILD0;
} else if (dir === childDir1) {
2019-05-31 19:33:56 +03:00
return CHILD1;
} else if (dir === otherDir) {
2019-05-31 19:33:56 +03:00
return OTHER;
} else if (dir === parentDir) {
2019-05-31 19:33:56 +03:00
return PARENT;
} else {
2019-05-31 19:33:56 +03:00
throw new Error(`Unexpected path: ${dir}`);
}
2019-05-31 19:33:56 +03:00
};
2019-05-31 19:33:56 +03:00
const watcher0 = new MockWatcher(childDir0);
await registry.attach(watcher0);
2019-05-31 19:33:56 +03:00
const watcher1 = new MockWatcher(childDir1);
await registry.attach(watcher1);
2019-05-31 19:33:56 +03:00
const watcher2 = new MockWatcher(otherDir);
await registry.attach(watcher2);
2019-05-31 19:33:56 +03:00
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
2019-05-31 19:33:56 +03:00
const watcher = new MockWatcher(parentDir);
await registry.attach(watcher);
2019-05-31 19:33:56 +03:00
expect(watcher.native).toBe(PARENT);
2019-05-31 19:33:56 +03:00
expect(watcher0.native).toBe(PARENT);
expect(CHILD0.stopped).toBe(true);
expect(CHILD0.disposed).toBe(true);
2019-05-31 19:33:56 +03:00
expect(watcher1.native).toBe(PARENT);
expect(CHILD1.stopped).toBe(true);
expect(CHILD1.disposed).toBe(true);
2019-05-31 19:33:56 +03:00
expect(watcher2.native).toBe(OTHER);
expect(OTHER.stopped).toBe(false);
expect(OTHER.disposed).toBe(false);
});
2019-05-31 19:33:56 +03:00
describe('removing NativeWatchers', function() {
it('happens when they stop', async function() {
const STOPPED = new MockNative('stopped');
const RUNNING = new MockNative('running');
2019-05-31 19:33:56 +03:00
const stoppedPath = absolute('watcher', 'that', 'will', 'be', 'stopped');
2019-02-22 10:55:17 +03:00
const stoppedPathParts = stoppedPath
.split(path.sep)
2019-05-31 19:33:56 +03:00
.filter(part => part.length > 0);
2019-02-22 10:55:17 +03:00
const runningPath = absolute(
'watcher',
'that',
'will',
'continue',
'to',
'exist'
2019-05-31 19:33:56 +03:00
);
2019-02-22 10:55:17 +03:00
const runningPathParts = runningPath
.split(path.sep)
2019-05-31 19:33:56 +03:00
.filter(part => part.length > 0);
createNative = dir => {
2017-08-01 18:21:56 +03:00
if (dir === stoppedPath) {
2019-05-31 19:33:56 +03:00
return STOPPED;
2017-08-01 18:21:56 +03:00
} else if (dir === runningPath) {
2019-05-31 19:33:56 +03:00
return RUNNING;
} else {
2019-05-31 19:33:56 +03:00
throw new Error(`Unexpected path: ${dir}`);
}
2019-05-31 19:33:56 +03:00
};
2019-05-31 19:33:56 +03:00
const stoppedWatcher = new MockWatcher(stoppedPath);
await registry.attach(stoppedWatcher);
2019-05-31 19:33:56 +03:00
const runningWatcher = new MockWatcher(runningPath);
await registry.attach(runningWatcher);
2017-06-21 18:08:29 +03:00
2019-05-31 19:33:56 +03:00
STOPPED.stop();
2017-06-21 18:08:29 +03:00
2017-08-01 18:21:56 +03:00
const runningNode = registry.tree.root.lookup(runningPathParts).when({
2017-06-21 18:08:29 +03:00
parent: node => node,
missing: () => false,
children: () => false
2019-05-31 19:33:56 +03:00
});
expect(runningNode).toBeTruthy();
expect(runningNode.getNativeWatcher()).toBe(RUNNING);
2017-06-21 18:08:29 +03:00
2017-08-01 18:21:56 +03:00
const stoppedNode = registry.tree.root.lookup(stoppedPathParts).when({
2017-06-21 18:08:29 +03:00
parent: () => false,
missing: () => true,
children: () => false
2019-05-31 19:33:56 +03:00
});
expect(stoppedNode).toBe(true);
});
2017-06-21 18:08:29 +03:00
2019-05-31 19:33:56 +03:00
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');
2017-06-21 18:08:29 +03:00
2019-05-31 19:33:56 +03:00
const parentDir = absolute('parent');
const childDir0 = path.join(parentDir, 'child0');
const childDir1 = path.join(parentDir, 'child1');
createNative = dir => {
if (dir === parentDir) {
2019-05-31 19:33:56 +03:00
return PARENT;
} else if (dir === childDir0) {
2019-05-31 19:33:56 +03:00
return CHILD0;
} else if (dir === childDir1) {
2019-05-31 19:33:56 +03:00
return CHILD1;
} else {
2019-05-31 19:33:56 +03:00
throw new Error(`Unexpected directory ${dir}`);
}
2019-05-31 19:33:56 +03:00
};
2019-05-31 19:33:56 +03:00
const parentWatcher = new MockWatcher(parentDir);
const childWatcher0 = new MockWatcher(childDir0);
const childWatcher1 = new MockWatcher(childDir1);
2019-05-31 19:33:56 +03:00
await registry.attach(parentWatcher);
await Promise.all([
registry.attach(childWatcher0),
registry.attach(childWatcher1)
2019-05-31 19:33:56 +03:00
]);
// All three watchers should share the parent watcher's native watcher.
2019-05-31 19:33:56 +03:00
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.
2019-05-31 19:33:56 +03:00
PARENT.stop();
2019-05-31 19:33:56 +03:00
expect(childWatcher0.native).toBe(CHILD0);
expect(childWatcher1.native).toBe(CHILD1);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(parentDir)).when({
parent: () => false,
missing: () => false,
children: () => true
})
2019-05-31 19:33:56 +03:00
).toBe(true);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(childDir0)).when({
parent: () => true,
missing: () => false,
children: () => false
})
2019-05-31 19:33:56 +03:00
).toBe(true);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(childDir1)).when({
parent: () => true,
missing: () => false,
children: () => false
})
2019-05-31 19:33:56 +03:00
).toBe(true);
});
2019-05-31 19:33:56 +03:00
it('consolidates children when splitting a parent watcher', async function() {
const CHILD0 = new MockNative('child0');
const PARENT = new MockNative('parent');
2019-05-31 19:33:56 +03:00
const parentDir = absolute('parent');
const childDir0 = path.join(parentDir, 'child0');
const childDir1 = path.join(parentDir, 'child0', 'child1');
createNative = dir => {
if (dir === parentDir) {
2019-05-31 19:33:56 +03:00
return PARENT;
} else if (dir === childDir0) {
2019-05-31 19:33:56 +03:00
return CHILD0;
} else {
2019-05-31 19:33:56 +03:00
throw new Error(`Unexpected directory ${dir}`);
}
2019-05-31 19:33:56 +03:00
};
2019-05-31 19:33:56 +03:00
const parentWatcher = new MockWatcher(parentDir);
const childWatcher0 = new MockWatcher(childDir0);
const childWatcher1 = new MockWatcher(childDir1);
2019-05-31 19:33:56 +03:00
await registry.attach(parentWatcher);
await Promise.all([
registry.attach(childWatcher0),
registry.attach(childWatcher1)
2019-05-31 19:33:56 +03:00
]);
// All three watchers should share the parent watcher's native watcher.
2019-05-31 19:33:56 +03:00
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.
2019-05-31 19:33:56 +03:00
PARENT.stop();
2019-05-31 19:33:56 +03:00
expect(childWatcher0.native).toBe(CHILD0);
expect(childWatcher1.native).toBe(CHILD0);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(parentDir)).when({
parent: () => false,
missing: () => false,
children: () => true
})
2019-05-31 19:33:56 +03:00
).toBe(true);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(childDir0)).when({
parent: () => true,
missing: () => false,
children: () => false
})
2019-05-31 19:33:56 +03:00
).toBe(true);
2019-02-22 10:55:17 +03:00
expect(
registry.tree.root.lookup(parts(childDir1)).when({
parent: () => true,
missing: () => false,
children: () => false
})
2019-05-31 19:33:56 +03:00
).toBe(true);
});
});
});