mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge pull request #14853 from atom/aw-filewatcher
Filesystem watcher API
This commit is contained in:
@@ -20,6 +20,11 @@ export function afterEach (fn) {
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
@@ -29,7 +34,7 @@ export function afterEach (fn) {
|
||||
}
|
||||
})
|
||||
|
||||
export async function conditionPromise (condition) {
|
||||
export async function conditionPromise (condition) {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
@@ -40,7 +45,7 @@ export async function conditionPromise (condition) {
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > 5000) {
|
||||
throw new Error("Timed out waiting on condition")
|
||||
throw new Error('Timed out waiting on condition')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,3 +77,27 @@ export function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ StorageFolder = require '../src/storage-folder'
|
||||
|
||||
describe "AtomEnvironment", ->
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe 'window sizing methods', ->
|
||||
describe '::getPosition and ::setPosition', ->
|
||||
|
||||
@@ -86,7 +86,11 @@ describe("AtomPaths", () => {
|
||||
afterEach(() => {
|
||||
delete process.env.ATOM_HOME
|
||||
fs.removeSync(electronUserDataPath)
|
||||
temp.cleanupSync()
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
app.setPath('userData', defaultElectronUserDataPath)
|
||||
})
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ describe "Babel transpiler support", ->
|
||||
|
||||
afterEach ->
|
||||
CompileCache.setCacheDirectory(originalCacheDir)
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe 'when a .js file starts with /** @babel */;', ->
|
||||
it "transpiles it using babel", ->
|
||||
|
||||
@@ -21,7 +21,8 @@ describe "CommandInstaller on #darwin", ->
|
||||
spyOn(CommandInstaller::, 'getInstallDirectory').andReturn(installationPath)
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
it "shows an error dialog when installing commands interactively fails", ->
|
||||
appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"])
|
||||
|
||||
@@ -23,7 +23,8 @@ describe 'CompileCache', ->
|
||||
afterEach ->
|
||||
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
|
||||
CSON.setCacheDir(CompileCache.getCacheDirectory())
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe 'addPathToCache(filePath, atomHome)', ->
|
||||
describe 'when the given file is plain javascript', ->
|
||||
|
||||
@@ -10,7 +10,8 @@ describe "DefaultDirectoryProvider", ->
|
||||
tmp = temp.mkdirSync('atom-spec-default-dir-provider')
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe ".directoryForURISync(uri)", ->
|
||||
it "returns a Directory with a path that matches the uri", ->
|
||||
|
||||
@@ -12,7 +12,8 @@ describe "GitRepositoryProvider", ->
|
||||
provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm)
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe ".repositoryForDirectory(directory)", ->
|
||||
describe "when specified a Directory with a Git repository", ->
|
||||
|
||||
@@ -24,7 +24,8 @@ describe "the `grammars` global", ->
|
||||
afterEach ->
|
||||
atom.packages.deactivatePackages()
|
||||
atom.packages.unloadPackages()
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe ".selectGrammar(filePath)", ->
|
||||
it "always returns a grammar", ->
|
||||
|
||||
@@ -508,7 +508,8 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
|
||||
function makeTempDir (name) {
|
||||
return fs.realpathSync(require('temp').mkdirSync(name))
|
||||
const temp = require('temp').track()
|
||||
return fs.realpathSync(temp.mkdirSync(name))
|
||||
}
|
||||
|
||||
let channelIdCounter = 0
|
||||
|
||||
@@ -16,7 +16,11 @@ describe("FileRecoveryService", () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
temp.cleanupSync()
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
})
|
||||
|
||||
describe("when no crash happens during a save", () => {
|
||||
|
||||
@@ -9,7 +9,8 @@ describe 'ModuleCache', ->
|
||||
spyOn(Module, '_findPath').andCallThrough()
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
it 'resolves Electron module paths without hitting the filesystem', ->
|
||||
builtins = ModuleCache.cache.builtins
|
||||
|
||||
362
spec/native-watcher-registry-spec.js
Normal file
362
spec/native-watcher-registry-spec.js
Normal file
@@ -0,0 +1,362 @@
|
||||
/** @babel */
|
||||
|
||||
import {it, beforeEach} from './async-spec-helpers'
|
||||
|
||||
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 () {
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,8 @@ describe "PackageManager", ->
|
||||
spyOn(ModuleCache, 'add')
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe "::getApmPath()", ->
|
||||
it "returns the path to the apm command", ->
|
||||
|
||||
186
spec/path-watcher-spec.js
Normal file
186
spec/path-watcher-spec.js
Normal file
@@ -0,0 +1,186 @@
|
||||
/** @babel */
|
||||
|
||||
import {it, beforeEach, afterEach, promisifySome} from './async-spec-helpers'
|
||||
import tempCb from 'temp'
|
||||
import fsCb from 'fs-plus'
|
||||
import path from 'path'
|
||||
|
||||
import {CompositeDisposable} from 'event-kit'
|
||||
import {watchPath, stopAllWatchers} from '../src/path-watcher'
|
||||
|
||||
tempCb.track()
|
||||
|
||||
const fs = promisifySome(fsCb, ['writeFile', 'mkdir', 'symlink', 'appendFile', 'realpath'])
|
||||
const temp = promisifySome(tempCb, ['mkdir'])
|
||||
|
||||
describe('watchPath', function () {
|
||||
let subs
|
||||
|
||||
beforeEach(function () {
|
||||
subs = new CompositeDisposable()
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
subs.dispose()
|
||||
await stopAllWatchers()
|
||||
})
|
||||
|
||||
function waitForChanges (watcher, ...fileNames) {
|
||||
const waiting = new Set(fileNames)
|
||||
let fired = false
|
||||
const relevantEvents = []
|
||||
|
||||
return new Promise(resolve => {
|
||||
const sub = watcher.onDidChange(events => {
|
||||
for (const event of events) {
|
||||
if (waiting.delete(event.path)) {
|
||||
relevantEvents.push(event)
|
||||
}
|
||||
}
|
||||
|
||||
if (!fired && waiting.size === 0) {
|
||||
fired = true
|
||||
resolve(relevantEvents)
|
||||
sub.dispose()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('watchPath()', function () {
|
||||
it('resolves getStartPromise() when the watcher begins listening', async function () {
|
||||
const rootDir = await temp.mkdir('atom-fsmanager-test-')
|
||||
|
||||
const watcher = watchPath(rootDir, {}, () => {})
|
||||
await watcher.getStartPromise()
|
||||
})
|
||||
|
||||
it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () {
|
||||
const rootDir = await temp.mkdir('atom-fsmanager-test-')
|
||||
|
||||
const watcher0 = watchPath(rootDir, {}, () => {})
|
||||
await watcher0.getStartPromise()
|
||||
|
||||
const watcher1 = watchPath(rootDir, {}, () => {})
|
||||
await watcher1.getStartPromise()
|
||||
|
||||
expect(watcher0.native).toBe(watcher1.native)
|
||||
})
|
||||
|
||||
it("reuses existing native watchers even while they're still starting", async function () {
|
||||
const rootDir = await temp.mkdir('atom-fsmanager-test-')
|
||||
|
||||
const watcher0 = watchPath(rootDir, {}, () => {})
|
||||
await watcher0.getAttachedPromise()
|
||||
expect(watcher0.native.isRunning()).toBe(false)
|
||||
|
||||
const watcher1 = watchPath(rootDir, {}, () => {})
|
||||
await watcher1.getAttachedPromise()
|
||||
|
||||
expect(watcher0.native).toBe(watcher1.native)
|
||||
|
||||
await Promise.all([watcher0.getStartPromise(), watcher1.getStartPromise()])
|
||||
})
|
||||
|
||||
it("doesn't attach new watchers to a native watcher that's stopping", async function () {
|
||||
const rootDir = await temp.mkdir('atom-fsmanager-test-')
|
||||
|
||||
const watcher0 = watchPath(rootDir, {}, () => {})
|
||||
await watcher0.getStartPromise()
|
||||
const native0 = watcher0.native
|
||||
|
||||
watcher0.dispose()
|
||||
|
||||
const watcher1 = watchPath(rootDir, {}, () => {})
|
||||
|
||||
expect(watcher1.native).not.toBe(native0)
|
||||
})
|
||||
|
||||
it('reuses an existing native watcher on a parent directory and filters events', async function () {
|
||||
const rootDir = await temp.mkdir('atom-fsmanager-test-').then(fs.realpath)
|
||||
const rootFile = path.join(rootDir, 'rootfile.txt')
|
||||
const subDir = path.join(rootDir, 'subdir')
|
||||
const subFile = path.join(subDir, 'subfile.txt')
|
||||
|
||||
await fs.mkdir(subDir)
|
||||
|
||||
// Keep the watchers alive with an undisposed subscription
|
||||
const rootWatcher = watchPath(rootDir, {}, () => {})
|
||||
const childWatcher = watchPath(subDir, {}, () => {})
|
||||
|
||||
await Promise.all([
|
||||
rootWatcher.getStartPromise(),
|
||||
childWatcher.getStartPromise()
|
||||
])
|
||||
|
||||
expect(rootWatcher.native).toBe(childWatcher.native)
|
||||
expect(rootWatcher.native.isRunning()).toBe(true)
|
||||
|
||||
const firstChanges = Promise.all([
|
||||
waitForChanges(rootWatcher, subFile),
|
||||
waitForChanges(childWatcher, subFile)
|
||||
])
|
||||
|
||||
await fs.writeFile(subFile, 'subfile\n', {encoding: 'utf8'})
|
||||
await firstChanges
|
||||
|
||||
const nextRootEvent = waitForChanges(rootWatcher, rootFile)
|
||||
await fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'})
|
||||
|
||||
await nextRootEvent
|
||||
})
|
||||
|
||||
it('adopts existing child watchers and filters events appropriately to them', async function () {
|
||||
const parentDir = await temp.mkdir('atom-fsmanager-test-').then(fs.realpath)
|
||||
|
||||
// Create the directory tree
|
||||
const rootFile = path.join(parentDir, 'rootfile.txt')
|
||||
const subDir0 = path.join(parentDir, 'subdir0')
|
||||
const subFile0 = path.join(subDir0, 'subfile0.txt')
|
||||
const subDir1 = path.join(parentDir, 'subdir1')
|
||||
const subFile1 = path.join(subDir1, 'subfile1.txt')
|
||||
|
||||
await fs.mkdir(subDir0)
|
||||
await fs.mkdir(subDir1)
|
||||
await Promise.all([
|
||||
fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'}),
|
||||
fs.writeFile(subFile0, 'subfile 0\n', {encoding: 'utf8'}),
|
||||
fs.writeFile(subFile1, 'subfile 1\n', {encoding: 'utf8'})
|
||||
])
|
||||
|
||||
// Begin the child watchers and keep them alive
|
||||
const subWatcher0 = watchPath(subDir0, {}, () => {})
|
||||
const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0)
|
||||
|
||||
const subWatcher1 = watchPath(subDir1, {}, () => {})
|
||||
const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1)
|
||||
|
||||
await Promise.all(
|
||||
[subWatcher0, subWatcher1].map(watcher => watcher.getStartPromise())
|
||||
)
|
||||
expect(subWatcher0.native).not.toBe(subWatcher1.native)
|
||||
|
||||
// Create the parent watcher
|
||||
const parentWatcher = watchPath(parentDir, {}, () => {})
|
||||
const parentWatcherChanges = waitForChanges(parentWatcher, rootFile, subFile0, subFile1)
|
||||
|
||||
await parentWatcher.getStartPromise()
|
||||
|
||||
expect(subWatcher0.native).toBe(parentWatcher.native)
|
||||
expect(subWatcher1.native).toBe(parentWatcher.native)
|
||||
|
||||
// Ensure events are filtered correctly
|
||||
await Promise.all([
|
||||
fs.appendFile(rootFile, 'change\n', {encoding: 'utf8'}),
|
||||
fs.appendFile(subFile0, 'change\n', {encoding: 'utf8'}),
|
||||
fs.appendFile(subFile1, 'change\n', {encoding: 'utf8'})
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
subWatcherChanges0,
|
||||
subWatcherChanges1,
|
||||
parentWatcherChanges
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,7 @@ Project = require '../src/project'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
{Directory} = require 'pathwatcher'
|
||||
{stopAllWatchers} = require '../src/path-watcher'
|
||||
GitRepository = require '../src/git-repository'
|
||||
|
||||
describe "Project", ->
|
||||
@@ -13,9 +14,6 @@ describe "Project", ->
|
||||
# Wait for project's service consumers to be asynchronously added
|
||||
waits(1)
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
|
||||
describe "serialization", ->
|
||||
deserializedProject = null
|
||||
|
||||
@@ -548,6 +546,59 @@ describe "Project", ->
|
||||
atom.project.removePath(ftpURI)
|
||||
expect(atom.project.getPaths()).toEqual []
|
||||
|
||||
describe ".onDidChangeFiles()", ->
|
||||
sub = []
|
||||
events = []
|
||||
checkCallback = ->
|
||||
|
||||
beforeEach ->
|
||||
sub = atom.project.onDidChangeFiles (incoming) ->
|
||||
events.push incoming...
|
||||
checkCallback()
|
||||
|
||||
afterEach ->
|
||||
sub.dispose()
|
||||
|
||||
waitForEvents = (paths) ->
|
||||
remaining = new Set(fs.realpathSync(p) for p in paths)
|
||||
new Promise (resolve, reject) ->
|
||||
checkCallback = ->
|
||||
remaining.delete(event.path) for event in events
|
||||
resolve() if remaining.size is 0
|
||||
|
||||
expire = ->
|
||||
checkCallback = ->
|
||||
console.error "Paths not seen:", Array.from(remaining)
|
||||
reject(new Error('Expired before all expected events were delivered.'))
|
||||
|
||||
checkCallback()
|
||||
setTimeout expire, 2000
|
||||
|
||||
it "reports filesystem changes within project paths", ->
|
||||
dirOne = temp.mkdirSync('atom-spec-project-one')
|
||||
fileOne = path.join(dirOne, 'file-one.txt')
|
||||
fileTwo = path.join(dirOne, 'file-two.txt')
|
||||
dirTwo = temp.mkdirSync('atom-spec-project-two')
|
||||
fileThree = path.join(dirTwo, 'file-three.txt')
|
||||
|
||||
# Ensure that all preexisting watchers are stopped
|
||||
waitsForPromise -> stopAllWatchers()
|
||||
|
||||
runs -> atom.project.setPaths([dirOne])
|
||||
waitsForPromise -> atom.project.watchersByPath[dirOne].getStartPromise()
|
||||
|
||||
runs ->
|
||||
expect(atom.project.watchersByPath[dirTwo]).toEqual undefined
|
||||
|
||||
fs.writeFileSync fileThree, "three\n"
|
||||
fs.writeFileSync fileTwo, "two\n"
|
||||
fs.writeFileSync fileOne, "one\n"
|
||||
|
||||
waitsForPromise -> waitForEvents [fileOne, fileTwo]
|
||||
|
||||
runs ->
|
||||
expect(events.some (event) -> event.path is fileThree).toBeFalsy()
|
||||
|
||||
describe ".onDidAddBuffer()", ->
|
||||
it "invokes the callback with added text buffers", ->
|
||||
buffers = []
|
||||
|
||||
@@ -37,7 +37,8 @@ describe "Windows Squirrel Update", ->
|
||||
WinShell.folderBackgroundContextMenu = new FakeShellOption()
|
||||
|
||||
afterEach ->
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
it "quits the app on all squirrel events", ->
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
|
||||
@@ -15,7 +15,11 @@ describe('StyleManager', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
temp.cleanupSync()
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
})
|
||||
|
||||
describe('::addStyleSheet(source, params)', () => {
|
||||
|
||||
@@ -9,7 +9,8 @@ describe "atom.themes", ->
|
||||
|
||||
afterEach ->
|
||||
atom.themes.deactivateThemes()
|
||||
temp.cleanupSync()
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe "theme getters and setters", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const TitleBar = require('../src/title-bar')
|
||||
const temp = require('temp')
|
||||
const temp = require('temp').track()
|
||||
|
||||
describe('TitleBar', () => {
|
||||
it('updates its title when document.title changes', () => {
|
||||
|
||||
@@ -28,7 +28,11 @@ describe('updateProcessEnv(launchEnv)', function () {
|
||||
}
|
||||
process.env = originalProcessEnv
|
||||
process.platform = originalProcessPlatform
|
||||
temp.cleanupSync()
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
})
|
||||
|
||||
describe('when the launch environment appears to come from a shell', function () {
|
||||
|
||||
@@ -9,7 +9,13 @@ const {Disposable} = require('event-kit')
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
|
||||
describe('WorkspaceElement', () => {
|
||||
afterEach(() => { temp.cleanupSync() })
|
||||
afterEach(() => {
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
})
|
||||
|
||||
describe('when the workspace element is focused', () => {
|
||||
it('transfers focus to the active pane', () => {
|
||||
|
||||
@@ -25,7 +25,13 @@ describe('Workspace', () => {
|
||||
waitsForPromise(() => atom.workspace.itemLocationStore.clear())
|
||||
})
|
||||
|
||||
afterEach(() => temp.cleanupSync())
|
||||
afterEach(() => {
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
})
|
||||
|
||||
function simulateReload() {
|
||||
waitsForPromise(() => {
|
||||
|
||||
Reference in New Issue
Block a user