Skip to content

Commit

Permalink
Tests for nsfw library (#133615)
Browse files Browse the repository at this point in the history
* watcher - add some basic tests (#132483)

* 💄

* wait for watcher to be ready

* flaky

* fix

* enable case rename on windows
  • Loading branch information
bpasero authored Sep 22, 2021
1 parent 0ff2fcb commit fcfe5a6
Show file tree
Hide file tree
Showing 26 changed files with 276 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
private readonly _onDidLogMessage = this._register(new Emitter<ILogMessage>());
readonly onDidLogMessage = this._onDidLogMessage.event;

private readonly watchers = new Map<string, IWatcher>();
protected readonly watchers = new Map<string, IWatcher>();

private verboseLogging = false;
private enospcErrorLogged = false;
Expand Down Expand Up @@ -281,6 +281,8 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
try {
const realpath = realpathSync(request.path);
if (realpath === request.path) {
this.warn(`ignoring a path for watching who's parent is already watched: ${request.path}`);

continue; // path is not a symbolic link or similar
}
} catch (error) {
Expand Down

This file was deleted.

272 changes: 272 additions & 0 deletions src/vs/platform/files/test/node/recursiveWatcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { Promises } from 'vs/base/node/pfs';
import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils';
import { FileChangeType } from 'vs/platform/files/common/files';
import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService';
import { IWatchRequest } from 'vs/platform/files/node/watcher/watcher';

flakySuite('Recursive Watcher', () => {

class TestNsfwWatcherService extends NsfwWatcherService {

testNormalizePaths(paths: string[]): string[] {

// Work with strings as paths to simplify testing
const requests: IWatchRequest[] = paths.map(path => {
return { path, excludes: [] };
});

return this.normalizeRequests(requests).map(request => request.path);
}

override async watch(requests: IWatchRequest[]): Promise<void> {
await super.watch(requests);

for (const [, watcher] of this.watchers) {
await watcher.instance;
}
}
}

let testDir: string;
let service: TestNsfwWatcherService;
let enableLogging = false;

setup(async () => {
service = new TestNsfwWatcherService();

if (enableLogging) {
service.onDidLogMessage(e => console.log(`[recursive watcher test message] ${e.message}`));
}

testDir = getRandomTestPath(tmpdir(), 'vsctests', 'filewatcher');

const sourceDir = getPathFromAmdModule(require, './fixtures/service');

await Promises.copy(sourceDir, testDir, { preserveSymlinks: false });
});

teardown(async () => {
await service.stop();

return Promises.rm(testDir);
});

function awaitEvent(service: TestNsfwWatcherService, path: string, type: FileChangeType): Promise<void> {
return new Promise(resolve => {
const disposable = service.onDidChangeFile(events => {
for (const event of events) {
if (event.path === path && event.type === type) {
disposable.dispose();
resolve();
break;
}
}
});
});
}

const runWatchTests = process.env['BUILD_SOURCEVERSION'] || process.env['CI'] || !!process.env['VSCODE_RUN_RECURSIVE_WATCH_TESTS'];

(runWatchTests ? test : test.skip)('basics', async function () {
await service.watch([{ path: testDir, excludes: [] }]);

// New file
const newFilePath = join(testDir, 'deep', 'newFile.txt');
let changeFuture: Promise<unknown> = awaitEvent(service, newFilePath, FileChangeType.ADDED);
await Promises.writeFile(newFilePath, 'Hello World');
await changeFuture;

// New folder
const newFolderPath = join(testDir, 'deep', 'New Folder');
changeFuture = awaitEvent(service, newFolderPath, FileChangeType.ADDED);
await Promises.mkdir(newFolderPath);
await changeFuture;

// Rename file
let renamedFilePath = join(testDir, 'deep', 'renamedFile.txt');
changeFuture = Promise.all([
awaitEvent(service, newFilePath, FileChangeType.DELETED),
awaitEvent(service, renamedFilePath, FileChangeType.ADDED)
]);
await Promises.rename(newFilePath, renamedFilePath);
await changeFuture;

// Rename folder
let renamedFolderPath = join(testDir, 'deep', 'Renamed Folder');
changeFuture = Promise.all([
awaitEvent(service, newFolderPath, FileChangeType.DELETED),
awaitEvent(service, renamedFolderPath, FileChangeType.ADDED)
]);
await Promises.rename(newFolderPath, renamedFolderPath);
await changeFuture;

// TODO: case rename is currently broken on macOS
if (isWindows) {

// Rename file (same name, different case)
const caseRenamedFilePath = join(testDir, 'deep', 'RenamedFile.txt');
changeFuture = Promise.all([
awaitEvent(service, renamedFilePath, FileChangeType.DELETED),
awaitEvent(service, caseRenamedFilePath, FileChangeType.ADDED)
]);
await Promises.rename(renamedFilePath, caseRenamedFilePath);
await changeFuture;
renamedFilePath = caseRenamedFilePath;

// Rename folder (same name, different case)
const caseRenamedFolderPath = join(testDir, 'deep', 'REnamed Folder');
changeFuture = Promise.all([
awaitEvent(service, renamedFolderPath, FileChangeType.DELETED),
awaitEvent(service, caseRenamedFolderPath, FileChangeType.ADDED)
]);
await Promises.rename(renamedFolderPath, caseRenamedFolderPath);
await changeFuture;
renamedFolderPath = caseRenamedFolderPath;
}

// Move file
const movedFilepath = join(testDir, 'movedFile.txt');
changeFuture = Promise.all([
awaitEvent(service, renamedFilePath, FileChangeType.DELETED),
awaitEvent(service, movedFilepath, FileChangeType.ADDED)
]);
await Promises.rename(renamedFilePath, movedFilepath);
await changeFuture;

// Move folder
const movedFolderpath = join(testDir, 'Moved Folder');
changeFuture = Promise.all([
awaitEvent(service, renamedFolderPath, FileChangeType.DELETED),
awaitEvent(service, movedFolderpath, FileChangeType.ADDED)
]);
await Promises.rename(renamedFolderPath, movedFolderpath);
await changeFuture;

// Copy file
const copiedFilepath = join(testDir, 'deep', 'copiedFile.txt');
changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.ADDED);
await Promises.copyFile(movedFilepath, copiedFilepath);
await changeFuture;

// Copy folder
const copiedFolderpath = join(testDir, 'deep', 'Copied Folder');
changeFuture = awaitEvent(service, copiedFolderpath, FileChangeType.ADDED);
await Promises.copy(movedFolderpath, copiedFolderpath, { preserveSymlinks: false });
await changeFuture;

// Change file
changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.UPDATED);
await Promises.writeFile(copiedFilepath, 'Hello Change');
await changeFuture;

// Delete file
changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.DELETED);
await Promises.unlink(copiedFilepath);
await changeFuture;

// Delete folder
changeFuture = awaitEvent(service, copiedFolderpath, FileChangeType.DELETED);
await Promises.rmdir(copiedFolderpath);
await changeFuture;
});

(runWatchTests ? test : test.skip)('subsequent watch updates watchers', async function () {
await service.watch([{ path: testDir, excludes: ['**/*.js'] }]);

// New file (*.txt)
let newTextFilePath = join(testDir, 'deep', 'newFile.txt');
let changeFuture: Promise<unknown> = awaitEvent(service, newTextFilePath, FileChangeType.ADDED);
await Promises.writeFile(newTextFilePath, 'Hello World');
await changeFuture;

await service.watch([{ path: join(testDir, 'deep'), excludes: ['**/*.js'] }]);
newTextFilePath = join(testDir, 'deep', 'newFile2.txt');
changeFuture = awaitEvent(service, newTextFilePath, FileChangeType.ADDED);
await Promises.writeFile(newTextFilePath, 'Hello World');
await changeFuture;

await service.watch([{ path: join(testDir, 'deep'), excludes: ['**/*.txt'] }]);
await service.watch([{ path: join(testDir, 'deep'), excludes: [] }]);
newTextFilePath = join(testDir, 'deep', 'newFile3.txt');
changeFuture = awaitEvent(service, newTextFilePath, FileChangeType.ADDED);
await Promises.writeFile(newTextFilePath, 'Hello World');
await changeFuture;

return service.stop();
});

((isWindows /* windows: cannot create file symbolic link without elevated context */ || !runWatchTests) ? test.skip : test)('symlink support (root)', async function () {
const link = join(testDir, 'deep-linked');
const linkTarget = join(testDir, 'deep');
await Promises.symlink(linkTarget, link);

await service.watch([{ path: link, excludes: [] }]);

// New file
const newFilePath = join(link, 'newFile.txt');
let changeFuture: Promise<unknown> = awaitEvent(service, newFilePath, FileChangeType.ADDED);
await Promises.writeFile(newFilePath, 'Hello World');
await changeFuture;
});

((isWindows /* windows: cannot create file symbolic link without elevated context */ || !runWatchTests) ? test.skip : test)('symlink support (via extra watch)', async function () {
const link = join(testDir, 'deep-linked');
const linkTarget = join(testDir, 'deep');
await Promises.symlink(linkTarget, link);

await service.watch([{ path: testDir, excludes: [] }, { path: link, excludes: [] }]);

// New file
const newFilePath = join(link, 'newFile.txt');
let changeFuture: Promise<unknown> = awaitEvent(service, newFilePath, FileChangeType.ADDED);
await Promises.writeFile(newFilePath, 'Hello World');
await changeFuture;
});

((isLinux /* linux: is case sensitive */ || !runWatchTests) ? test.skip : test)('wrong casing', async function () {
const deepWrongCasedPath = join(testDir, 'DEEP');

await service.watch([{ path: deepWrongCasedPath, excludes: [] }]);

// New file
const newFilePath = join(deepWrongCasedPath, 'newFile.txt');
let changeFuture: Promise<unknown> = awaitEvent(service, newFilePath, FileChangeType.ADDED);
await Promises.writeFile(newFilePath, 'Hello World');
await changeFuture;
});

test('should not exclude roots that do not overlap', () => {
if (isWindows) {
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a']), ['C:\\a']);
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b']), ['C:\\a', 'C:\\b']);
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\c\\d\\e']), ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']);
} else {
assert.deepStrictEqual(service.testNormalizePaths(['/a']), ['/a']);
assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b']), ['/a', '/b']);
assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b', '/c/d/e']), ['/a', '/b', '/c/d/e']);
}
});

test('should remove sub-folders of other paths', () => {
if (isWindows) {
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\a\\b']), ['C:\\a']);
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']);
assert.deepStrictEqual(service.testNormalizePaths(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']);
assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d']), ['C:\\a']);
} else {
assert.deepStrictEqual(service.testNormalizePaths(['/a', '/a/b']), ['/a']);
assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b', '/a/b']), ['/a', '/b']);
assert.deepStrictEqual(service.testNormalizePaths(['/b/a', '/a', '/b', '/a/b']), ['/a', '/b']);
assert.deepStrictEqual(service.testNormalizePaths(['/a', '/a/b', '/a/c/d']), ['/a']);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum Path {
UNC
}

suite('Normalizer', () => {
suite('Watcher Events Normalizer', () => {

test('simple add/update/delete', function (done: () => void) {
const watch = new TestFileWatcher();
Expand Down

0 comments on commit fcfe5a6

Please sign in to comment.