backupFileService.test.ts 23.5 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 16
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
17
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
18
import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model';
19
import { Schemas } from 'vs/base/common/network';
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 { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
25
import { IFileService } from 'vs/platform/files/common/files';
26
import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
27
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
B
Benjamin Pasero 已提交
28
import { VSBuffer } from 'vs/base/common/buffer';
29
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
30

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

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

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

49
	constructor(backupPath: string) {
50
		super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': userdataDir });
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 TestWorkbenchEnvironmentService(workspaceBackupPath);
64 65 66
		const logService = new NullLogService();
		const fileService = new FileService(logService);
		const diskFileSystemProvider = new DiskFileSystemProvider(logService);
67
		fileService.registerProvider(Schemas.file, diskFileSystemProvider);
S
Sandeep Somavarapu 已提交
68
		fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(workspaceBackupPath), diskFileSystemProvider, environmentService, logService));
69

70
		super(environmentService, fileService, logService);
71 72

		this.fileService = fileService;
73 74
		this.backupResourceJoiners = [];
		this.discardBackupJoiners = [];
75
		this.discardedBackups = [];
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
		this.discardedBackups.push(resource);
97 98 99 100 101

		while (this.discardBackupJoiners.length) {
			this.discardBackupJoiners.pop()!();
		}
	}
102 103 104 105 106 107 108 109

	async getBackupContents(resource: URI): Promise<string> {
		const backupResource = this.toBackupResource(resource);

		const fileContents = await this.fileService.readFile(backupResource);

		return fileContents.value.toString();
	}
110 111
}

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

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

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

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

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

R
Rob Lourens 已提交
129 130
	suite('hashPath', () => {
		test('should correctly hash the path for untitled scheme URIs', () => {
B
Benjamin Pasero 已提交
131
			const uri = URI.from({
R
Rob Lourens 已提交
132 133 134 135 136 137 138 139 140 141
				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 已提交
142
			const uri = URI.file('/foo');
R
Rob Lourens 已提交
143 144 145 146 147 148 149 150 151 152 153
			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 已提交
154 155 156 157
	suite('getBackupResource', () => {
		test('should get the correct backup path for text files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
			const backupResource = fooFile;
158 159
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
S
Sandeep Somavarapu 已提交
160
			const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
161
			assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
D
Daniel Imms 已提交
162
		});
163

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

174
	suite('backup', () => {
175 176 177 178 179 180 181 182
		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));
		});

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

		test('text file (with version)', async () => {
B
Benjamin Pasero 已提交
192
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), 666);
193 194 195 196 197
			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));
198 199
		});

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

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

216
		test('text file (ITextSnapshot)', async () => {
217
			const model = createTextModel('test');
218

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

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

228
		test('untitled file (ITextSnapshot)', async () => {
229
			const model = createTextModel('test');
230

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

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

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

B
Benjamin Pasero 已提交
243
			await service.backup(fooFile, model.createSnapshot());
244 245 246
			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}`);
247 248
			assert.ok(service.hasBackupSync(fooFile));

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

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

B
Benjamin Pasero 已提交
256
			await service.backup(untitledFile, model.createSnapshot());
257 258 259
			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}`);
260 261
			assert.ok(service.hasBackupSync(untitledFile));

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

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

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

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

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
	suite('discardBackups', () => {
		test('text file', async () => {
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
			await service.discardBackups();
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.existsSync(barBackupPath), false);
			assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
		});

		test('untitled file', async () => {
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			await service.discardBackups();
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
		});

		test('can backup after discarding all', async () => {
			await service.discardBackups();
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			assert.equal(fs.existsSync(workspaceBackupPath), true);
		});
	});

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

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

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

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

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

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

349 350 351 352 353 354 355 356 357 358 359 360
			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
			};
361

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

365
		test('should restore the original contents (text file)', async () => {
366 367 368 369
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
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 430
				'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 ',
431
				'adipiscing ßß elit',
432
				'consectetur '
433 434
			].join('');

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

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

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

451 452 453 454
			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);
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 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);
		});

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
		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 已提交
491
		});
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510

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

511
		test('should ignore invalid backups', async () => {
B
Benjamin Pasero 已提交
512 513
			const contents = 'test\nand more stuff';

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

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

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

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

530
			assert.ok(!err);
B
Benjamin Pasero 已提交
531 532
		});

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

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

540 541 542
			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)));
543 544

			if (expectedMeta) {
545 546 547 548
				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);
549
			} else {
550
				assert.ok(!backup!.meta);
551 552
			}
		}
D
Daniel Imms 已提交
553
	});
D
Daniel Imms 已提交
554
});
D
Daniel Imms 已提交
555

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

558
	let service: NodeTestBackupFileService;
559 560

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

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

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

		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);
586
		assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
B
Benjamin Pasero 已提交
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607

		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 已提交
608 609 610
		const resource2 = URI.file('test1.html');
		const resource3 = URI.file('test2.html');
		const resource4 = URI.file('test3.html');
B
Benjamin Pasero 已提交
611 612 613

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

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

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

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

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

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

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

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

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

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