backupFileService.test.ts 23.7 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  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';
D
Daniel Imms 已提交
7
import * as platform from 'vs/base/common/platform';
R
Rob Lourens 已提交
8
import * as crypto from 'crypto';
9 10
import * as os from 'os';
import * as fs from 'fs';
11
import * as path from 'vs/base/common/path';
12
import * as pfs from 'vs/base/node/pfs';
B
Benjamin Pasero 已提交
13
import { URI } from 'vs/base/common/uri';
14
import { BackupFilesModel } from 'vs/workbench/services/backup/common/backupFileService';
15
import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel';
16
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
17
import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model';
18
import { Schemas } from 'vs/base/common/network';
B
Benjamin Pasero 已提交
19
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
20
import { FileService } from 'vs/platform/files/common/fileService';
21
import { NullLogService } from 'vs/platform/log/common/log';
22
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
23
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
24
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
25
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
26
import { IFileService } from 'vs/platform/files/common/files';
27
import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
28 29
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
B
Benjamin Pasero 已提交
30
import { VSBuffer } from 'vs/base/common/buffer';
31

32 33 34
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
const appSettingsHome = path.join(userdataDir, 'User');
const backupHome = path.join(userdataDir, 'Backups');
D
Daniel Imms 已提交
35 36
const workspacesJsonPath = path.join(backupHome, 'workspaces.json');

B
Benjamin Pasero 已提交
37
const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace');
38
const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource));
B
Benjamin Pasero 已提交
39 40
const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo');
const customFile = URI.parse('customScheme://some/path');
41
const customFileWithFragment = URI.parse('customScheme2://some/path#fragment');
B
Benjamin Pasero 已提交
42 43 44
const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar');
const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar');
const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
45 46 47
const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
D
Daniel Imms 已提交
48

49
class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService {
B
Benjamin Pasero 已提交
50

51
	constructor(backupPath: string) {
52
		super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0);
B
Benjamin Pasero 已提交
53 54 55
	}
}

56
export class NodeTestBackupFileService extends BackupFileService {
57 58 59

	readonly fileService: IFileService;

60 61 62
	private backupResourceJoiners: Function[];
	private discardBackupJoiners: Function[];

63
	constructor(workspaceBackupPath: string) {
64
		const environmentService = new TestBackupEnvironmentService(workspaceBackupPath);
65 66 67
		const fileService = new FileService(new NullLogService());
		const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
		fileService.registerProvider(Schemas.file, diskFileSystemProvider);
S
Sandeep Somavarapu 已提交
68
		fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService));
69

70
		super(environmentService, fileService);
71 72

		this.fileService = fileService;
73 74
		this.backupResourceJoiners = [];
		this.discardBackupJoiners = [];
75
		this.didDiscardAllWorkspaceBackups = false;
76 77
	}

78 79 80 81
	joinBackupResource(): Promise<void> {
		return new Promise(resolve => this.backupResourceJoiners.push(resolve));
	}

82
	async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any): Promise<void> {
B
Benjamin Pasero 已提交
83
		await super.backup(resource, content, versionId, meta);
84 85 86 87 88 89 90 91 92 93

		while (this.backupResourceJoiners.length) {
			this.backupResourceJoiners.pop()!();
		}
	}

	joinDiscardBackup(): Promise<void> {
		return new Promise(resolve => this.discardBackupJoiners.push(resolve));
	}

B
Benjamin Pasero 已提交
94 95
	async discardBackup(resource: URI): Promise<void> {
		await super.discardBackup(resource);
96 97 98 99 100

		while (this.discardBackupJoiners.length) {
			this.discardBackupJoiners.pop()!();
		}
	}
101 102 103

	didDiscardAllWorkspaceBackups: boolean;

104 105
	shutdown(options?: { dicardAllBackups: boolean }): Promise<void> {
		this.didDiscardAllWorkspaceBackups = !!options?.dicardAllBackups;
106

107
		return super.shutdown(options);
108
	}
109 110
}

D
Daniel Imms 已提交
111
suite('BackupFileService', () => {
112
	let service: NodeTestBackupFileService;
113

114
	setup(async () => {
115
		service = new NodeTestBackupFileService(workspaceBackupPath);
116

117
		// Delete any existing backups completely and then re-create it.
118 119 120 121
		await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
		await pfs.mkdirp(backupHome);

		return pfs.writeFile(workspacesJsonPath, '');
122 123
	});

124
	teardown(() => {
B
Benjamin Pasero 已提交
125
		return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
126 127
	});

R
Rob Lourens 已提交
128 129
	suite('hashPath', () => {
		test('should correctly hash the path for untitled scheme URIs', () => {
B
Benjamin Pasero 已提交
130
			const uri = URI.from({
R
Rob Lourens 已提交
131 132 133 134 135 136 137 138 139 140
				scheme: 'untitled',
				path: 'Untitled-1'
			});
			const actual = hashPath(uri);
			// If these hashes change people will lose their backed up files!
			assert.equal(actual, '13264068d108c6901b3592ea654fcd57');
			assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
		});

		test('should correctly hash the path for file scheme URIs', () => {
B
Benjamin Pasero 已提交
141
			const uri = URI.file('/foo');
R
Rob Lourens 已提交
142 143 144 145 146 147 148 149 150 151 152
			const actual = hashPath(uri);
			// If these hashes change people will lose their backed up files!
			if (platform.isWindows) {
				assert.equal(actual, 'dec1a583f52468a020bd120c3f01d812');
			} else {
				assert.equal(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf');
			}
			assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
		});
	});

D
Daniel Imms 已提交
153 154 155 156
	suite('getBackupResource', () => {
		test('should get the correct backup path for text files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
			const backupResource = fooFile;
157 158
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
159 160
			const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
			assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
D
Daniel Imms 已提交
161
		});
162

D
Daniel Imms 已提交
163 164
		test('should get the correct backup path for untitled files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePath>
B
Benjamin Pasero 已提交
165
			const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
166 167
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
168 169
			const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString();
			assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
D
Daniel Imms 已提交
170
		});
171 172
	});

173
	suite('backup', () => {
174 175 176 177 178 179 180 181
		test('no text', async () => {
			await service.backup(fooFile);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n`);
			assert.ok(service.hasBackupSync(fooFile));
		});

182
		test('text file', async () => {
B
Benjamin Pasero 已提交
183
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
184 185 186
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
187 188 189 190
			assert.ok(service.hasBackupSync(fooFile));
		});

		test('text file (with version)', async () => {
B
Benjamin Pasero 已提交
191
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), 666);
192 193 194 195 196
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
			assert.ok(!service.hasBackupSync(fooFile, 555));
			assert.ok(service.hasBackupSync(fooFile, 666));
197 198
		});

199
		test('text file (with meta)', async () => {
B
Benjamin Pasero 已提交
200
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), undefined, { etag: '678', orphaned: true });
201 202
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
203
			assert.equal(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`);
204
			assert.ok(service.hasBackupSync(fooFile));
205 206
		});

207
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
208
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
209 210 211
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
212
			assert.ok(service.hasBackupSync(untitledFile));
213
		});
214

215
		test('text file (ITextSnapshot)', async () => {
216 217
			const model = TextModel.createFromString('test');

B
Benjamin Pasero 已提交
218
			await service.backup(fooFile, model.createSnapshot());
219 220 221
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
222 223
			assert.ok(service.hasBackupSync(fooFile));

224
			model.dispose();
225 226
		});

227
		test('untitled file (ITextSnapshot)', async () => {
228 229
			const model = TextModel.createFromString('test');

B
Benjamin Pasero 已提交
230
			await service.backup(untitledFile, model.createSnapshot());
231 232 233
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
234

235
			model.dispose();
236 237
		});

238
		test('text file (large file, ITextSnapshot)', async () => {
B
Benjamin Pasero 已提交
239
			const largeString = (new Array(10 * 1024)).join('Large String\n');
240 241
			const model = TextModel.createFromString(largeString);

B
Benjamin Pasero 已提交
242
			await service.backup(fooFile, model.createSnapshot());
243 244 245
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n${largeString}`);
246 247
			assert.ok(service.hasBackupSync(fooFile));

248
			model.dispose();
249 250
		});

251
		test('untitled file (large file, ITextSnapshot)', async () => {
B
Benjamin Pasero 已提交
252
			const largeString = (new Array(10 * 1024)).join('Large String\n');
253 254
			const model = TextModel.createFromString(largeString);

B
Benjamin Pasero 已提交
255
			await service.backup(untitledFile, model.createSnapshot());
256 257 258
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\n${largeString}`);
259 260
			assert.ok(service.hasBackupSync(untitledFile));

261
			model.dispose();
262
		});
263 264
	});

265
	suite('discardBackup', () => {
266
		test('text file', async () => {
B
Benjamin Pasero 已提交
267
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
268
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
269 270
			assert.ok(service.hasBackupSync(fooFile));

B
Benjamin Pasero 已提交
271
			await service.discardBackup(fooFile);
272 273
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
274
			assert.ok(!service.hasBackupSync(fooFile));
275 276
		});

277
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
278
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
279
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
B
Benjamin Pasero 已提交
280
			await service.discardBackup(untitledFile);
281 282
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
283 284
		});
	});
B
Benjamin Pasero 已提交
285

286
	suite('discardBackups', () => {
287
		test('text file', async () => {
B
Benjamin Pasero 已提交
288
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
289
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
B
Benjamin Pasero 已提交
290
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
291
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
292
			await service.shutdown({ dicardAllBackups: true });
293 294 295
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.existsSync(barBackupPath), false);
			assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
D
Daniel Imms 已提交
296 297
		});

298
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
299
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
300
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
301
			await service.shutdown({ dicardAllBackups: true });
302 303
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
304
		});
305

306
		test('should disable further backups', async () => {
307
			await service.shutdown({ dicardAllBackups: true });
B
Benjamin Pasero 已提交
308
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
309
			assert.equal(fs.existsSync(workspaceBackupPath), false);
310
		});
311 312
	});

313
	suite('getBackups', () => {
314
		test('("file") - text file', async () => {
B
Benjamin Pasero 已提交
315 316
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
317
			assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
B
Benjamin Pasero 已提交
318 319
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles_1 = await service.getBackups();
320
			assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
D
Daniel Imms 已提交
321 322
		});

323
		test('("file") - untitled file', async () => {
B
Benjamin Pasero 已提交
324 325
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
326
			assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
D
Daniel Imms 已提交
327 328
		});

329
		test('("untitled") - untitled file', async () => {
B
Benjamin Pasero 已提交
330 331
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
332
			assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
333 334 335
		});
	});

336
	suite('resolve', () => {
337 338 339 340 341 342 343 344

		interface IBackupTestMetaData {
			mtime?: number;
			size?: number;
			etag?: string;
			orphaned?: boolean;
		}

345
		test('should restore the original contents (untitled file)', async () => {
346
			const contents = 'test\nand more stuff';
347

348 349 350 351 352 353 354 355 356 357 358 359
			await testResolveBackup(untitledFile, contents);
		});

		test('should restore the original contents (untitled file with metadata)', async () => {
			const contents = 'test\nand more stuff';

			const meta = {
				etag: 'the Etag',
				size: 666,
				mtime: Date.now(),
				orphaned: true
			};
360

361
			await testResolveBackup(untitledFile, contents, meta);
362 363
		});

364
		test('should restore the original contents (text file)', async () => {
365 366 367 368
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
				'adipiscing ßß elit'
			].join('');

			await testResolveBackup(fooFile, contents);
		});

		test('should restore the original contents (text file - custom scheme)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
				'adipiscing ßß elit'
			].join('');

			await testResolveBackup(customFile, contents);
		});

		test('should restore the original contents (text file with metadata)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'adipiscing ßß elit',
				'consectetur '
			].join('');

			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};

			await testResolveBackup(fooFile, contents, meta);
		});

		test('should restore the original contents (text file with metadata changed once)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'adipiscing ßß elit',
				'consectetur '
			].join('');

			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};

			await testResolveBackup(fooFile, contents, meta);

			// Change meta and test again
			meta.size = 999;
			await testResolveBackup(fooFile, contents, meta);
		});

		test('should restore the original contents (text file with broken metadata)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
430
				'adipiscing ßß elit',
431
				'consectetur '
432 433
			].join('');

434 435 436 437 438 439
			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};
440

B
Benjamin Pasero 已提交
441
			await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
442 443 444 445 446 447 448 449

			const fileContents = fs.readFileSync(fooBackupPath).toString();
			assert.equal(fileContents.indexOf(fooFile.toString()), 0);

			const metaIndex = fileContents.indexOf('{');
			const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex);
			fs.writeFileSync(fooBackupPath, newFileContents);

450 451 452 453
			const backup = await service.resolve(fooFile);
			assert.ok(backup);
			assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true)));
			assert.ok(!backup!.meta);
454 455
		});

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
		test('should restore the original contents (text file with metadata and fragment URI)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'adipiscing ßß elit',
				'consectetur '
			].join('');

			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};

			await testResolveBackup(customFileWithFragment, contents, meta);
		});

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
		test('should restore the original contents (text file with space in name with metadata)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'adipiscing ßß elit',
				'consectetur '
			].join('');

			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};

			await testResolveBackup(fooBarFile, contents, meta);
D
Daniel Imms 已提交
490
		});
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

		test('should restore the original contents (text file with too large metadata to persist)', async () => {
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'adipiscing ßß elit',
				'consectetur '
			].join('');

			const meta = {
				etag: (new Array(100 * 1024)).join('Large String'),
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};

			await testResolveBackup(fooBarFile, contents, meta, null);
		});

B
Benjamin Pasero 已提交
510 511 512
		test('should throw an error when restoring invalid backup', async () => {
			const contents = 'test\nand more stuff';

B
Benjamin Pasero 已提交
513
			await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1);
B
Benjamin Pasero 已提交
514

515
			const backup = await service.resolve(fooBarFile);
B
Benjamin Pasero 已提交
516 517 518 519
			if (!backup) {
				throw new Error('Unexpected missing backup');
			}

520
			await service.fileService.writeFile(service.toBackupResource(fooBarFile), VSBuffer.fromString(''));
B
Benjamin Pasero 已提交
521 522 523

			let err: Error;
			try {
524
				await service.resolve<IBackupTestMetaData>(fooBarFile);
B
Benjamin Pasero 已提交
525 526 527 528 529 530 531
			} catch (error) {
				err = error;
			}

			assert.ok(err!);
		});

B
Benjamin Pasero 已提交
532
		async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) {
533 534 535 536
			if (typeof expectedMeta === 'undefined') {
				expectedMeta = meta;
			}

B
Benjamin Pasero 已提交
537
			await service.backup(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
538

539 540 541
			const backup = await service.resolve<IBackupTestMetaData>(resource);
			assert.ok(backup);
			assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true)));
542 543

			if (expectedMeta) {
544 545 546 547
				assert.equal(backup!.meta!.etag, expectedMeta.etag);
				assert.equal(backup!.meta!.size, expectedMeta.size);
				assert.equal(backup!.meta!.mtime, expectedMeta.mtime);
				assert.equal(backup!.meta!.orphaned, expectedMeta.orphaned);
548
			} else {
549
				assert.ok(!backup!.meta);
550 551
			}
		}
D
Daniel Imms 已提交
552
	});
D
Daniel Imms 已提交
553
});
D
Daniel Imms 已提交
554

D
Daniel Imms 已提交
555
suite('BackupFilesModel', () => {
556

557
	let service: NodeTestBackupFileService;
558 559

	setup(async () => {
560
		service = new NodeTestBackupFileService(workspaceBackupPath);
561 562 563 564 565 566 567 568 569 570 571 572

		// Delete any existing backups completely and then re-create it.
		await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
		await pfs.mkdirp(backupHome);

		return pfs.writeFile(workspacesJsonPath, '');
	});

	teardown(() => {
		return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
	});

D
Daniel Imms 已提交
573
	test('simple', () => {
574
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
575

B
Benjamin Pasero 已提交
576
		const resource1 = URI.file('test.html');
B
Benjamin Pasero 已提交
577 578 579 580 581 582 583 584

		assert.equal(model.has(resource1), false);

		model.add(resource1);

		assert.equal(model.has(resource1), true);
		assert.equal(model.has(resource1, 0), true);
		assert.equal(model.has(resource1, 1), false);
585
		assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
B
Benjamin Pasero 已提交
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

		model.remove(resource1);

		assert.equal(model.has(resource1), false);

		model.add(resource1);

		assert.equal(model.has(resource1), true);
		assert.equal(model.has(resource1, 0), true);
		assert.equal(model.has(resource1, 1), false);

		model.clear();

		assert.equal(model.has(resource1), false);

		model.add(resource1, 1);

		assert.equal(model.has(resource1), true);
		assert.equal(model.has(resource1, 0), false);
		assert.equal(model.has(resource1, 1), true);

B
Benjamin Pasero 已提交
607 608 609
		const resource2 = URI.file('test1.html');
		const resource3 = URI.file('test2.html');
		const resource4 = URI.file('test3.html');
B
Benjamin Pasero 已提交
610 611 612

		model.add(resource2);
		model.add(resource3);
613
		model.add(resource4, undefined, { foo: 'bar' });
B
Benjamin Pasero 已提交
614 615 616 617

		assert.equal(model.has(resource1), true);
		assert.equal(model.has(resource2), true);
		assert.equal(model.has(resource3), true);
618

B
Benjamin Pasero 已提交
619
		assert.equal(model.has(resource4), true);
620 621
		assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true);
		assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false);
B
Benjamin Pasero 已提交
622 623
	});

624 625 626
	test('resolve', async () => {
		await pfs.mkdirp(path.dirname(fooBackupPath));
		fs.writeFileSync(fooBackupPath, 'foo');
627
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
628

B
Benjamin Pasero 已提交
629 630
		const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
		assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true);
B
Benjamin Pasero 已提交
631
	});
632

D
Daniel Imms 已提交
633
	test('get', () => {
634
		const model = new BackupFilesModel(service.fileService);
635

636
		assert.deepEqual(model.get(), []);
637

B
Benjamin Pasero 已提交
638 639 640
		const file1 = URI.file('/root/file/foo.html');
		const file2 = URI.file('/root/file/bar.html');
		const untitled = URI.file('/root/untitled/bar.html');
641

D
Daniel Imms 已提交
642 643 644 645
		model.add(file1);
		model.add(file2);
		model.add(untitled);

646
		assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
647
	});
648
});