backupFileService.test.ts 21.9 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';
19
import { FileService } from 'vs/platform/files/common/fileService';
20
import { NullLogService } from 'vs/platform/log/common/log';
21
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
22
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
23
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
24
import { IFileService } from 'vs/platform/files/common/files';
25
import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
26 27
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
B
Benjamin Pasero 已提交
28
import { VSBuffer } from 'vs/base/common/buffer';
29
import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
30

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

B
Benjamin Pasero 已提交
36
const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace');
37
const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource));
B
Benjamin Pasero 已提交
38 39
const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo');
const customFile = URI.parse('customScheme://some/path');
40
const customFileWithFragment = URI.parse('customScheme2://some/path#fragment');
B
Benjamin Pasero 已提交
41 42 43
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' });
44 45
const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
D
Daniel Imms 已提交
46

47
class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService {
B
Benjamin Pasero 已提交
48

49
	constructor(backupPath: string) {
M
Martin Aeschlimann 已提交
50
		super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId);
B
Benjamin Pasero 已提交
51 52 53
	}
}

54
export class NodeTestBackupFileService extends BackupFileService {
55 56 57

	readonly fileService: IFileService;

58 59
	private backupResourceJoiners: Function[];
	private discardBackupJoiners: Function[];
60
	discardedBackups: URI[];
61

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

69
		super(environmentService, fileService);
70 71

		this.fileService = fileService;
72 73
		this.backupResourceJoiners = [];
		this.discardBackupJoiners = [];
74
		this.discardedBackups = [];
75 76
	}

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

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

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

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

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

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

D
Daniel Imms 已提交
103
suite('BackupFileService', () => {
104
	let service: NodeTestBackupFileService;
105

106
	setup(async () => {
107
		service = new NodeTestBackupFileService(workspaceBackupPath);
108

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

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

116
	teardown(() => {
B
Benjamin Pasero 已提交
117
		return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
118 119
	});

R
Rob Lourens 已提交
120 121
	suite('hashPath', () => {
		test('should correctly hash the path for untitled scheme URIs', () => {
B
Benjamin Pasero 已提交
122
			const uri = URI.from({
R
Rob Lourens 已提交
123 124 125 126 127 128 129 130 131 132
				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 已提交
133
			const uri = URI.file('/foo');
R
Rob Lourens 已提交
134 135 136 137 138 139 140 141 142 143 144
			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 已提交
145 146 147 148
	suite('getBackupResource', () => {
		test('should get the correct backup path for text files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
			const backupResource = fooFile;
149 150
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
151 152
			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 已提交
153
		});
154

D
Daniel Imms 已提交
155 156
		test('should get the correct backup path for untitled files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePath>
B
Benjamin Pasero 已提交
157
			const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
158 159
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
160 161
			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 已提交
162
		});
163 164
	});

165
	suite('backup', () => {
166 167 168 169 170 171 172 173
		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));
		});

174
		test('text file', async () => {
B
Benjamin Pasero 已提交
175
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
176 177 178
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
179 180 181 182
			assert.ok(service.hasBackupSync(fooFile));
		});

		test('text file (with version)', async () => {
B
Benjamin Pasero 已提交
183
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), 666);
184 185 186 187 188
			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));
189 190
		});

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

199
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
200
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
201 202 203
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
204
			assert.ok(service.hasBackupSync(untitledFile));
205
		});
206

207
		test('text file (ITextSnapshot)', async () => {
208 209
			const model = TextModel.createFromString('test');

B
Benjamin Pasero 已提交
210
			await service.backup(fooFile, model.createSnapshot());
211 212 213
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
214 215
			assert.ok(service.hasBackupSync(fooFile));

216
			model.dispose();
217 218
		});

219
		test('untitled file (ITextSnapshot)', async () => {
220 221
			const model = TextModel.createFromString('test');

B
Benjamin Pasero 已提交
222
			await service.backup(untitledFile, model.createSnapshot());
223 224 225
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
226

227
			model.dispose();
228 229
		});

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

B
Benjamin Pasero 已提交
234
			await service.backup(fooFile, model.createSnapshot());
235 236 237
			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}`);
238 239
			assert.ok(service.hasBackupSync(fooFile));

240
			model.dispose();
241 242
		});

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

B
Benjamin Pasero 已提交
247
			await service.backup(untitledFile, model.createSnapshot());
248 249 250
			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}`);
251 252
			assert.ok(service.hasBackupSync(untitledFile));

253
			model.dispose();
254
		});
255 256
	});

257
	suite('discardBackup', () => {
258
		test('text file', async () => {
B
Benjamin Pasero 已提交
259
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
260
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
261 262
			assert.ok(service.hasBackupSync(fooFile));

B
Benjamin Pasero 已提交
263
			await service.discardBackup(fooFile);
264 265
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
266
			assert.ok(!service.hasBackupSync(fooFile));
267 268
		});

269
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
270
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
271
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
B
Benjamin Pasero 已提交
272
			await service.discardBackup(untitledFile);
273 274
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
275 276
		});
	});
B
Benjamin Pasero 已提交
277

278
	suite('getBackups', () => {
279
		test('("file") - text file', async () => {
B
Benjamin Pasero 已提交
280 281
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
282
			assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
B
Benjamin Pasero 已提交
283 284
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles_1 = await service.getBackups();
285
			assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
D
Daniel Imms 已提交
286 287
		});

288
		test('("file") - untitled file', async () => {
B
Benjamin Pasero 已提交
289 290
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
291
			assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
D
Daniel Imms 已提交
292 293
		});

294
		test('("untitled") - untitled file', async () => {
B
Benjamin Pasero 已提交
295 296
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
297
			assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
298 299 300
		});
	});

301
	suite('resolve', () => {
302 303 304 305 306 307 308 309

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

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

313 314 315 316 317 318 319 320 321 322 323 324
			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
			};
325

326
			await testResolveBackup(untitledFile, contents, meta);
327 328
		});

329
		test('should restore the original contents (text file)', async () => {
330 331 332 333
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 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
				'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 ',
395
				'adipiscing ßß elit',
396
				'consectetur '
397 398
			].join('');

399 400 401 402 403 404
			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};
405

B
Benjamin Pasero 已提交
406
			await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
407 408 409 410 411 412 413 414

			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);

415 416 417 418
			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);
419 420
		});

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
		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);
		});

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
		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 已提交
455
		});
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474

		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 已提交
475 476 477
		test('should throw an error when restoring invalid backup', async () => {
			const contents = 'test\nand more stuff';

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

480
			const backup = await service.resolve(fooBarFile);
B
Benjamin Pasero 已提交
481 482 483 484
			if (!backup) {
				throw new Error('Unexpected missing backup');
			}

485
			await service.fileService.writeFile(service.toBackupResource(fooBarFile), VSBuffer.fromString(''));
B
Benjamin Pasero 已提交
486 487 488

			let err: Error;
			try {
489
				await service.resolve<IBackupTestMetaData>(fooBarFile);
B
Benjamin Pasero 已提交
490 491 492 493 494 495 496
			} catch (error) {
				err = error;
			}

			assert.ok(err!);
		});

B
Benjamin Pasero 已提交
497
		async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) {
498 499 500 501
			if (typeof expectedMeta === 'undefined') {
				expectedMeta = meta;
			}

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

504 505 506
			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)));
507 508

			if (expectedMeta) {
509 510 511 512
				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);
513
			} else {
514
				assert.ok(!backup!.meta);
515 516
			}
		}
D
Daniel Imms 已提交
517
	});
D
Daniel Imms 已提交
518
});
D
Daniel Imms 已提交
519

D
Daniel Imms 已提交
520
suite('BackupFilesModel', () => {
521

522
	let service: NodeTestBackupFileService;
523 524

	setup(async () => {
525
		service = new NodeTestBackupFileService(workspaceBackupPath);
526 527 528 529 530 531 532 533 534 535 536 537

		// 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 已提交
538
	test('simple', () => {
539
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
540

B
Benjamin Pasero 已提交
541
		const resource1 = URI.file('test.html');
B
Benjamin Pasero 已提交
542 543 544 545 546 547 548 549

		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);
550
		assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
B
Benjamin Pasero 已提交
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571

		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 已提交
572 573 574
		const resource2 = URI.file('test1.html');
		const resource3 = URI.file('test2.html');
		const resource4 = URI.file('test3.html');
B
Benjamin Pasero 已提交
575 576 577

		model.add(resource2);
		model.add(resource3);
578
		model.add(resource4, undefined, { foo: 'bar' });
B
Benjamin Pasero 已提交
579 580 581 582

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

B
Benjamin Pasero 已提交
584
		assert.equal(model.has(resource4), true);
585 586
		assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true);
		assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false);
B
Benjamin Pasero 已提交
587 588
	});

589 590 591
	test('resolve', async () => {
		await pfs.mkdirp(path.dirname(fooBackupPath));
		fs.writeFileSync(fooBackupPath, 'foo');
592
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
593

B
Benjamin Pasero 已提交
594 595
		const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
		assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true);
B
Benjamin Pasero 已提交
596
	});
597

D
Daniel Imms 已提交
598
	test('get', () => {
599
		const model = new BackupFilesModel(service.fileService);
600

601
		assert.deepEqual(model.get(), []);
602

B
Benjamin Pasero 已提交
603 604 605
		const file1 = URI.file('/root/file/foo.html');
		const file2 = URI.file('/root/file/bar.html');
		const untitled = URI.file('/root/untitled/bar.html');
606

D
Daniel Imms 已提交
607 608 609 610
		model.add(file1);
		model.add(file2);
		model.add(untitled);

611
		assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
612
	});
613
});