Path: blob/main/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { IndexedDB } from '../../../../base/browser/indexedDB.js';7import { bufferToReadable, bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../../base/common/buffer.js';8import { DisposableStore } from '../../../../base/common/lifecycle.js';9import { Schemas } from '../../../../base/common/network.js';10import { basename, joinPath } from '../../../../base/common/resources.js';11import { URI } from '../../../../base/common/uri.js';12import { flakySuite } from '../../../../base/test/common/testUtils.js';13import { IndexedDBFileSystemProvider } from '../../browser/indexedDBFileSystemProvider.js';14import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType } from '../../common/files.js';15import { FileService } from '../../common/fileService.js';16import { NullLogService } from '../../../log/common/log.js';1718flakySuite('IndexedDBFileSystemProvider', function () {1920let service: FileService;21let userdataFileProvider: IndexedDBFileSystemProvider;22const testDir = '/';2324const userdataURIFromPaths = (paths: readonly string[]) => joinPath(URI.from({ scheme: Schemas.vscodeUserData, path: testDir }), ...paths);2526const disposables = new DisposableStore();2728const initFixtures = async () => {29await Promise.all(30[['fixtures', 'resolver', 'examples'],31['fixtures', 'resolver', 'other', 'deep'],32['fixtures', 'service', 'deep'],33['batched']]34.map(path => userdataURIFromPaths(path))35.map(uri => service.createFolder(uri)));36await Promise.all(37([38[['fixtures', 'resolver', 'examples', 'company.js'], 'class company {}'],39[['fixtures', 'resolver', 'examples', 'conway.js'], 'export function conway() {}'],40[['fixtures', 'resolver', 'examples', 'employee.js'], 'export const employee = "jax"'],41[['fixtures', 'resolver', 'examples', 'small.js'], ''],42[['fixtures', 'resolver', 'other', 'deep', 'company.js'], 'class company {}'],43[['fixtures', 'resolver', 'other', 'deep', 'conway.js'], 'export function conway() {}'],44[['fixtures', 'resolver', 'other', 'deep', 'employee.js'], 'export const employee = "jax"'],45[['fixtures', 'resolver', 'other', 'deep', 'small.js'], ''],46[['fixtures', 'resolver', 'index.html'], '<p>p</p>'],47[['fixtures', 'resolver', 'site.css'], '.p {color: red;}'],48[['fixtures', 'service', 'deep', 'company.js'], 'class company {}'],49[['fixtures', 'service', 'deep', 'conway.js'], 'export function conway() {}'],50[['fixtures', 'service', 'deep', 'employee.js'], 'export const employee = "jax"'],51[['fixtures', 'service', 'deep', 'small.js'], ''],52[['fixtures', 'service', 'binary.txt'], '<p>p</p>'],53] as const)54.map(([path, contents]) => [userdataURIFromPaths(path), contents] as const)55.map(([uri, contents]) => service.createFile(uri, VSBuffer.fromString(contents)))56);57};5859const reload = async () => {60const logService = new NullLogService();6162service = new FileService(logService);63disposables.add(service);6465const indexedDB = await IndexedDB.create('vscode-web-db-test', 1, ['vscode-userdata-store', 'vscode-logs-store']);6667userdataFileProvider = new IndexedDBFileSystemProvider(Schemas.vscodeUserData, indexedDB, 'vscode-userdata-store', true);68disposables.add(service.registerProvider(Schemas.vscodeUserData, userdataFileProvider));69disposables.add(userdataFileProvider);70};7172setup(async function () {73this.timeout(15000);74await reload();75});7677teardown(async () => {78await userdataFileProvider.reset();79disposables.clear();80});8182test('root is always present', async () => {83assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory);84await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false, atomic: false });85assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory);86});8788test('createFolder', async () => {89let event: FileOperationEvent | undefined;90disposables.add(service.onDidRunOperation(e => event = e));9192const parent = await service.resolve(userdataURIFromPaths([]));93const newFolderResource = joinPath(parent.resource, 'newFolder');9495assert.strictEqual((await userdataFileProvider.readdir(parent.resource)).length, 0);96const newFolder = await service.createFolder(newFolderResource);97assert.strictEqual(newFolder.name, 'newFolder');98assert.strictEqual((await userdataFileProvider.readdir(parent.resource)).length, 1);99assert.strictEqual((await userdataFileProvider.stat(newFolderResource)).type, FileType.Directory);100101assert.ok(event);102assert.strictEqual(event.resource.path, newFolderResource.path);103assert.strictEqual(event.operation, FileOperation.CREATE);104assert.strictEqual(event.target!.resource.path, newFolderResource.path);105assert.strictEqual(event.target!.isDirectory, true);106});107108test('createFolder: creating multiple folders at once', async () => {109let event: FileOperationEvent;110disposables.add(service.onDidRunOperation(e => event = e));111112const multiFolderPaths = ['a', 'couple', 'of', 'folders'];113const parent = await service.resolve(userdataURIFromPaths([]));114const newFolderResource = joinPath(parent.resource, ...multiFolderPaths);115116const newFolder = await service.createFolder(newFolderResource);117118const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1];119assert.strictEqual(newFolder.name, lastFolderName);120assert.strictEqual((await userdataFileProvider.stat(newFolderResource)).type, FileType.Directory);121122assert.ok(event!);123assert.strictEqual(event!.resource.path, newFolderResource.path);124assert.strictEqual(event!.operation, FileOperation.CREATE);125assert.strictEqual(event!.target!.resource.path, newFolderResource.path);126assert.strictEqual(event!.target!.isDirectory, true);127});128129test('exists', async () => {130let exists = await service.exists(userdataURIFromPaths([]));131assert.strictEqual(exists, true);132133exists = await service.exists(userdataURIFromPaths(['hello']));134assert.strictEqual(exists, false);135});136137test('resolve - file', async () => {138await initFixtures();139140const resource = userdataURIFromPaths(['fixtures', 'resolver', 'index.html']);141const resolved = await service.resolve(resource);142143assert.strictEqual(resolved.name, 'index.html');144assert.strictEqual(resolved.isFile, true);145assert.strictEqual(resolved.isDirectory, false);146assert.strictEqual(resolved.isSymbolicLink, false);147assert.strictEqual(resolved.resource.toString(), resource.toString());148assert.strictEqual(resolved.children, undefined);149assert.ok(resolved.size! > 0);150});151152test('resolve - directory', async () => {153await initFixtures();154155const testsElements = ['examples', 'other', 'index.html', 'site.css'];156157const resource = userdataURIFromPaths(['fixtures', 'resolver']);158const result = await service.resolve(resource);159160assert.ok(result);161assert.strictEqual(result.resource.toString(), resource.toString());162assert.strictEqual(result.name, 'resolver');163assert.ok(result.children);164assert.ok(result.children.length > 0);165assert.ok(result.isDirectory);166assert.strictEqual(result.children.length, testsElements.length);167168assert.ok(result.children.every(entry => {169return testsElements.some(name => {170return basename(entry.resource) === name;171});172}));173174result.children.forEach(value => {175assert.ok(basename(value.resource));176if (['examples', 'other'].indexOf(basename(value.resource)) >= 0) {177assert.ok(value.isDirectory);178assert.strictEqual(value.mtime, undefined);179assert.strictEqual(value.ctime, undefined);180} else if (basename(value.resource) === 'index.html') {181assert.ok(!value.isDirectory);182assert.ok(!value.children);183assert.strictEqual(value.mtime, undefined);184assert.strictEqual(value.ctime, undefined);185} else if (basename(value.resource) === 'site.css') {186assert.ok(!value.isDirectory);187assert.ok(!value.children);188assert.strictEqual(value.mtime, undefined);189assert.strictEqual(value.ctime, undefined);190} else {191assert.fail('Unexpected value ' + basename(value.resource));192}193});194});195196test('createFile', async () => {197return assertCreateFile(contents => VSBuffer.fromString(contents));198});199200test('createFile (readable)', async () => {201return assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents)));202});203204test('createFile (stream)', async () => {205return assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents)));206});207208async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {209let event: FileOperationEvent;210disposables.add(service.onDidRunOperation(e => event = e));211212const contents = 'Hello World';213const resource = userdataURIFromPaths(['test.txt']);214215assert.strictEqual(await service.canCreateFile(resource), true);216const fileStat = await service.createFile(resource, converter(contents));217assert.strictEqual(fileStat.name, 'test.txt');218assert.strictEqual((await userdataFileProvider.stat(fileStat.resource)).type, FileType.File);219assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(fileStat.resource)), contents);220221assert.ok(event!);222assert.strictEqual(event!.resource.path, resource.path);223assert.strictEqual(event!.operation, FileOperation.CREATE);224assert.strictEqual(event!.target!.resource.path, resource.path);225}226227const fileCreateBatchTester = (size: number, name: string) => {228const batch = Array.from({ length: size }).map((_, i) => ({ contents: `Hello${i}`, resource: userdataURIFromPaths(['batched', name, `Hello${i}.txt`]) }));229let creationPromises: Promise<any> | undefined = undefined;230return {231async create() {232return creationPromises = Promise.all(batch.map(entry => userdataFileProvider.writeFile(entry.resource, VSBuffer.fromString(entry.contents).buffer, { create: true, overwrite: true, unlock: false, atomic: false })));233},234async assertContentsCorrect() {235if (!creationPromises) { throw Error('read called before create'); }236await creationPromises;237await Promise.all(batch.map(async (entry, i) => {238assert.strictEqual((await userdataFileProvider.stat(entry.resource)).type, FileType.File);239assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(entry.resource)), entry.contents);240}));241}242};243};244245test('createFile - batch', async () => {246const tester = fileCreateBatchTester(20, 'batch');247await tester.create();248await tester.assertContentsCorrect();249});250251test('createFile - batch (mixed parallel/sequential)', async () => {252const batch1 = fileCreateBatchTester(1, 'batch1');253const batch2 = fileCreateBatchTester(20, 'batch2');254const batch3 = fileCreateBatchTester(1, 'batch3');255const batch4 = fileCreateBatchTester(20, 'batch4');256257batch1.create();258batch2.create();259await Promise.all([batch1.assertContentsCorrect(), batch2.assertContentsCorrect()]);260batch3.create();261batch4.create();262await Promise.all([batch3.assertContentsCorrect(), batch4.assertContentsCorrect()]);263await Promise.all([batch1.assertContentsCorrect(), batch2.assertContentsCorrect()]);264});265266test('rename not existing resource', async () => {267const parent = await service.resolve(userdataURIFromPaths([]));268const sourceFile = joinPath(parent.resource, 'sourceFile');269const targetFile = joinPath(parent.resource, 'targetFile');270271try {272await service.move(sourceFile, targetFile, false);273} catch (error) {274assert.deepStrictEqual((<FileSystemProviderError>error).code, FileSystemProviderErrorCode.FileNotFound);275return;276}277278assert.fail('This should fail with error');279});280281test('rename to an existing file without overwrite', async () => {282const parent = await service.resolve(userdataURIFromPaths([]));283const sourceFile = joinPath(parent.resource, 'sourceFile');284await service.writeFile(sourceFile, VSBuffer.fromString('This is source file'));285286const targetFile = joinPath(parent.resource, 'targetFile');287await service.writeFile(targetFile, VSBuffer.fromString('This is target file'));288289try {290await service.move(sourceFile, targetFile, false);291} catch (error) {292assert.deepStrictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);293return;294}295296assert.fail('This should fail with error');297});298299test('rename folder to an existing folder without overwrite', async () => {300const parent = await service.resolve(userdataURIFromPaths([]));301const sourceFolder = joinPath(parent.resource, 'sourceFolder');302await service.createFolder(sourceFolder);303const targetFolder = joinPath(parent.resource, 'targetFolder');304await service.createFolder(targetFolder);305306try {307await service.move(sourceFolder, targetFolder, false);308} catch (error) {309assert.deepStrictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);310return;311}312313assert.fail('This should fail with cannot overwrite error');314});315316test('rename file to a folder', async () => {317const parent = await service.resolve(userdataURIFromPaths([]));318const sourceFile = joinPath(parent.resource, 'sourceFile');319await service.writeFile(sourceFile, VSBuffer.fromString('This is source file'));320321const targetFolder = joinPath(parent.resource, 'targetFolder');322await service.createFolder(targetFolder);323324try {325await service.move(sourceFile, targetFolder, false);326} catch (error) {327assert.deepStrictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);328return;329}330331assert.fail('This should fail with error');332});333334test('rename folder to a file', async () => {335const parent = await service.resolve(userdataURIFromPaths([]));336const sourceFolder = joinPath(parent.resource, 'sourceFile');337await service.createFolder(sourceFolder);338339const targetFile = joinPath(parent.resource, 'targetFile');340await service.writeFile(targetFile, VSBuffer.fromString('This is target file'));341342try {343await service.move(sourceFolder, targetFile, false);344} catch (error) {345assert.deepStrictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);346return;347}348349assert.fail('This should fail with error');350});351352test('rename file', async () => {353const parent = await service.resolve(userdataURIFromPaths([]));354const sourceFile = joinPath(parent.resource, 'sourceFile');355await service.writeFile(sourceFile, VSBuffer.fromString('This is source file'));356357const targetFile = joinPath(parent.resource, 'targetFile');358await service.move(sourceFile, targetFile, false);359360const content = await service.readFile(targetFile);361assert.strictEqual(await service.exists(sourceFile), false);362assert.strictEqual(content.value.toString(), 'This is source file');363});364365test('rename to an existing file with overwrite', async () => {366const parent = await service.resolve(userdataURIFromPaths([]));367const sourceFile = joinPath(parent.resource, 'sourceFile');368const targetFile = joinPath(parent.resource, 'targetFile');369370await Promise.all([371service.writeFile(sourceFile, VSBuffer.fromString('This is source file')),372service.writeFile(targetFile, VSBuffer.fromString('This is target file'))373]);374375await service.move(sourceFile, targetFile, true);376377const content = await service.readFile(targetFile);378assert.strictEqual(await service.exists(sourceFile), false);379assert.strictEqual(content.value.toString(), 'This is source file');380});381382test('rename folder to a new folder', async () => {383const parent = await service.resolve(userdataURIFromPaths([]));384const sourceFolder = joinPath(parent.resource, 'sourceFolder');385await service.createFolder(sourceFolder);386387const targetFolder = joinPath(parent.resource, 'targetFolder');388await service.move(sourceFolder, targetFolder, false);389390assert.deepStrictEqual(await service.exists(sourceFolder), false);391assert.deepStrictEqual(await service.exists(targetFolder), true);392});393394test('rename folder to an existing folder', async () => {395const parent = await service.resolve(userdataURIFromPaths([]));396const sourceFolder = joinPath(parent.resource, 'sourceFolder');397await service.createFolder(sourceFolder);398const targetFolder = joinPath(parent.resource, 'targetFolder');399await service.createFolder(targetFolder);400401await service.move(sourceFolder, targetFolder, true);402403assert.deepStrictEqual(await service.exists(sourceFolder), false);404assert.deepStrictEqual(await service.exists(targetFolder), true);405});406407test('rename a folder that has multiple files and folders', async () => {408const parent = await service.resolve(userdataURIFromPaths([]));409410const sourceFolder = joinPath(parent.resource, 'sourceFolder');411const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1');412const sourceFile2 = joinPath(sourceFolder, 'folder2', 'file1');413const sourceEmptyFolder = joinPath(sourceFolder, 'folder3');414415await Promise.all([416service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')),417service.writeFile(sourceFile2, VSBuffer.fromString('Source File 2')),418service.createFolder(sourceEmptyFolder)419]);420421const targetFolder = joinPath(parent.resource, 'targetFolder');422const targetFile1 = joinPath(targetFolder, 'folder1', 'file1');423const targetFile2 = joinPath(targetFolder, 'folder2', 'file1');424const targetEmptyFolder = joinPath(targetFolder, 'folder3');425426await service.move(sourceFolder, targetFolder, false);427428assert.deepStrictEqual(await service.exists(sourceFolder), false);429assert.deepStrictEqual(await service.exists(targetFolder), true);430assert.strictEqual((await service.readFile(targetFile1)).value.toString(), 'Source File 1');431assert.strictEqual((await service.readFile(targetFile2)).value.toString(), 'Source File 2');432assert.deepStrictEqual(await service.exists(targetEmptyFolder), true);433});434435test('rename a folder to another folder that has some files', async () => {436const parent = await service.resolve(userdataURIFromPaths([]));437438const sourceFolder = joinPath(parent.resource, 'sourceFolder');439const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1');440441const targetFolder = joinPath(parent.resource, 'targetFolder');442const targetFile1 = joinPath(targetFolder, 'folder1', 'file1');443const targetFile2 = joinPath(targetFolder, 'folder1', 'file2');444const targetFile3 = joinPath(targetFolder, 'folder2', 'file1');445446await Promise.all([447service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')),448service.writeFile(targetFile2, VSBuffer.fromString('Target File 2')),449service.writeFile(targetFile3, VSBuffer.fromString('Target File 3'))450]);451452await service.move(sourceFolder, targetFolder, true);453454assert.deepStrictEqual(await service.exists(sourceFolder), false);455assert.deepStrictEqual(await service.exists(targetFolder), true);456assert.strictEqual((await service.readFile(targetFile1)).value.toString(), 'Source File 1');457assert.strictEqual(await service.exists(targetFile2), false);458assert.strictEqual(await service.exists(targetFile3), false);459});460461test('deleteFile', async () => {462await initFixtures();463464let event: FileOperationEvent;465disposables.add(service.onDidRunOperation(e => event = e));466467const anotherResource = userdataURIFromPaths(['fixtures', 'service', 'deep', 'company.js']);468const resource = userdataURIFromPaths(['fixtures', 'service', 'deep', 'conway.js']);469const source = await service.resolve(resource);470471assert.strictEqual(await service.canDelete(source.resource, { useTrash: false }), true);472await service.del(source.resource, { useTrash: false });473474assert.strictEqual(await service.exists(source.resource), false);475assert.strictEqual(await service.exists(anotherResource), true);476477assert.ok(event!);478assert.strictEqual(event!.resource.path, resource.path);479assert.strictEqual(event!.operation, FileOperation.DELETE);480481{482let error: Error | undefined = undefined;483try {484await service.del(source.resource, { useTrash: false });485} catch (e) {486error = e;487}488489assert.ok(error);490assert.strictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND);491}492await reload();493{494let error: Error | undefined = undefined;495try {496await service.del(source.resource, { useTrash: false });497} catch (e) {498error = e;499}500501assert.ok(error);502assert.strictEqual((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND);503}504});505506test('deleteFolder (recursive)', async () => {507await initFixtures();508let event: FileOperationEvent;509disposables.add(service.onDidRunOperation(e => event = e));510511const resource = userdataURIFromPaths(['fixtures', 'service', 'deep']);512const subResource1 = userdataURIFromPaths(['fixtures', 'service', 'deep', 'company.js']);513const subResource2 = userdataURIFromPaths(['fixtures', 'service', 'deep', 'conway.js']);514assert.strictEqual(await service.exists(subResource1), true);515assert.strictEqual(await service.exists(subResource2), true);516517const source = await service.resolve(resource);518519assert.strictEqual(await service.canDelete(source.resource, { recursive: true, useTrash: false }), true);520await service.del(source.resource, { recursive: true, useTrash: false });521522assert.strictEqual(await service.exists(source.resource), false);523assert.strictEqual(await service.exists(subResource1), false);524assert.strictEqual(await service.exists(subResource2), false);525assert.ok(event!);526assert.strictEqual(event!.resource.fsPath, resource.fsPath);527assert.strictEqual(event!.operation, FileOperation.DELETE);528});529530test('deleteFolder (non recursive)', async () => {531await initFixtures();532const resource = userdataURIFromPaths(['fixtures', 'service', 'deep']);533const source = await service.resolve(resource);534535assert.ok((await service.canDelete(source.resource)) instanceof Error);536537let error;538try {539await service.del(source.resource);540} catch (e) {541error = e;542}543assert.ok(error);544});545546test('delete empty folder', async () => {547const parent = await service.resolve(userdataURIFromPaths([]));548const folder = joinPath(parent.resource, 'folder');549await service.createFolder(folder);550551await service.del(folder);552553assert.deepStrictEqual(await service.exists(folder), false);554});555556test('delete empty folder with reccursive', async () => {557const parent = await service.resolve(userdataURIFromPaths([]));558const folder = joinPath(parent.resource, 'folder');559await service.createFolder(folder);560561await service.del(folder, { recursive: true });562563assert.deepStrictEqual(await service.exists(folder), false);564});565566test('deleteFolder with folders and files (recursive)', async () => {567const parent = await service.resolve(userdataURIFromPaths([]));568569const targetFolder = joinPath(parent.resource, 'targetFolder');570const file1 = joinPath(targetFolder, 'folder1', 'file1');571await service.createFile(file1);572const file2 = joinPath(targetFolder, 'folder2', 'file1');573await service.createFile(file2);574const emptyFolder = joinPath(targetFolder, 'folder3');575await service.createFolder(emptyFolder);576577await service.del(targetFolder, { recursive: true });578579assert.deepStrictEqual(await service.exists(targetFolder), false);580assert.deepStrictEqual(await service.exists(joinPath(targetFolder, 'folder1')), false);581assert.deepStrictEqual(await service.exists(joinPath(targetFolder, 'folder2')), false);582assert.deepStrictEqual(await service.exists(file1), false);583assert.deepStrictEqual(await service.exists(file2), false);584assert.deepStrictEqual(await service.exists(emptyFolder), false);585});586});587588589