backupFileService.test.ts 24.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, NativeBackupFileService } from 'vs/workbench/services/backup/electron-browser/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
import { TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
31
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
32
import { insert } from 'vs/base/common/arrays';
33

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

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

50
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
B
Benjamin Pasero 已提交
51

52
	constructor(backupPath: string) {
53
		super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': userdataDir }, TestProductService);
B
Benjamin Pasero 已提交
54 55 56
	}
}

57
export class NodeTestBackupFileService extends NativeBackupFileService {
58 59 60

	readonly fileService: IFileService;

61 62
	private backupResourceJoiners: Function[];
	private discardBackupJoiners: Function[];
63
	discardedBackups: URI[];
64
	private pendingBackupsArr: Promise<void>[];
65

66
	constructor(workspaceBackupPath: string) {
67
		const environmentService = new TestWorkbenchEnvironmentService(workspaceBackupPath);
68 69 70
		const logService = new NullLogService();
		const fileService = new FileService(logService);
		const diskFileSystemProvider = new DiskFileSystemProvider(logService);
71
		fileService.registerProvider(Schemas.file, diskFileSystemProvider);
72
		fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService));
73

74
		super(environmentService, fileService, logService);
75 76

		this.fileService = fileService;
77 78
		this.backupResourceJoiners = [];
		this.discardBackupJoiners = [];
79
		this.discardedBackups = [];
80 81 82 83 84
		this.pendingBackupsArr = [];
	}

	async waitForAllBackups(): Promise<void> {
		await Promise.all(this.pendingBackupsArr);
85 86
	}

87 88 89 90
	joinBackupResource(): Promise<void> {
		return new Promise(resolve => this.backupResourceJoiners.push(resolve));
	}

91
	async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any, token?: CancellationToken): Promise<void> {
92 93 94 95 96 97 98 99
		const p = super.backup(resource, content, versionId, meta, token);
		const removeFromPendingBackups = insert(this.pendingBackupsArr, p.then(undefined, undefined));

		try {
			await p;
		} finally {
			removeFromPendingBackups();
		}
100 101 102 103 104 105 106 107 108 109

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

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

B
Benjamin Pasero 已提交
110 111
	async discardBackup(resource: URI): Promise<void> {
		await super.discardBackup(resource);
112
		this.discardedBackups.push(resource);
113 114 115 116 117

		while (this.discardBackupJoiners.length) {
			this.discardBackupJoiners.pop()!();
		}
	}
118 119 120 121 122 123 124 125

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

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

		return fileContents.value.toString();
	}
126 127
}

D
Daniel Imms 已提交
128
suite('BackupFileService', () => {
129
	let service: NodeTestBackupFileService;
130

131
	setup(async () => {
132
		service = new NodeTestBackupFileService(workspaceBackupPath);
133

134
		// Delete any existing backups completely and then re-create it.
135
		await pfs.rimraf(backupHome);
136 137 138
		await pfs.mkdirp(backupHome);

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

141
	teardown(() => {
142
		return pfs.rimraf(backupHome);
143 144
	});

R
Rob Lourens 已提交
145 146
	suite('hashPath', () => {
		test('should correctly hash the path for untitled scheme URIs', () => {
B
Benjamin Pasero 已提交
147
			const uri = URI.from({
R
Rob Lourens 已提交
148 149 150 151 152 153 154 155 156 157
				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 已提交
158
			const uri = URI.file('/foo');
R
Rob Lourens 已提交
159 160 161 162 163 164 165 166 167 168 169
			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 已提交
170 171 172 173
	suite('getBackupResource', () => {
		test('should get the correct backup path for text files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
			const backupResource = fooFile;
174 175
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
S
Sandeep Somavarapu 已提交
176
			const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
177
			assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
D
Daniel Imms 已提交
178
		});
179

D
Daniel Imms 已提交
180 181
		test('should get the correct backup path for untitled files', () => {
			// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePath>
B
Benjamin Pasero 已提交
182
			const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
183 184
			const workspaceHash = hashPath(workspaceResource);
			const filePathHash = hashPath(backupResource);
S
Sandeep Somavarapu 已提交
185
			const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString();
186
			assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
D
Daniel Imms 已提交
187
		});
188 189
	});

190
	suite('backup', () => {
191 192 193 194 195 196 197 198
		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));
		});

199
		test('text file', async () => {
200
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
201 202 203
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
204 205 206 207
			assert.ok(service.hasBackupSync(fooFile));
		});

		test('text file (with version)', async () => {
208
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 666);
209 210 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`);
			assert.ok(!service.hasBackupSync(fooFile, 555));
			assert.ok(service.hasBackupSync(fooFile, 666));
214 215
		});

216
		test('text file (with meta)', async () => {
217
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), undefined, { etag: '678', orphaned: true });
218 219
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
220
			assert.equal(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`);
221
			assert.ok(service.hasBackupSync(fooFile));
222 223
		});

224
		test('untitled file', async () => {
225
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
226 227 228
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
			assert.equal(fs.existsSync(untitledBackupPath), true);
			assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
229
			assert.ok(service.hasBackupSync(untitledFile));
230
		});
231

232
		test('text file (ITextSnapshot)', async () => {
233
			const model = createTextModel('test');
234

B
Benjamin Pasero 已提交
235
			await service.backup(fooFile, model.createSnapshot());
236 237 238
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
			assert.equal(fs.existsSync(fooBackupPath), true);
			assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
239 240
			assert.ok(service.hasBackupSync(fooFile));

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

244
		test('untitled file (ITextSnapshot)', async () => {
245
			const model = createTextModel('test');
246

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()}\ntest`);
251

252
			model.dispose();
253 254
		});

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

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

265
			model.dispose();
266 267
		});

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

B
Benjamin Pasero 已提交
272
			await service.backup(untitledFile, model.createSnapshot());
273 274 275
			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}`);
276 277
			assert.ok(service.hasBackupSync(untitledFile));

278
			model.dispose();
279
		});
280 281 282 283 284 285 286 287 288 289

		test('cancellation', async () => {
			const cts = new CancellationTokenSource();
			const promise = service.backup(fooFile, undefined, undefined, undefined, cts.token);
			cts.cancel();
			await promise;

			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.ok(!service.hasBackupSync(fooFile));
		});
290 291
	});

292
	suite('discardBackup', () => {
293
		test('text file', async () => {
294
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
295
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
296 297
			assert.ok(service.hasBackupSync(fooFile));

B
Benjamin Pasero 已提交
298
			await service.discardBackup(fooFile);
299 300
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
301
			assert.ok(!service.hasBackupSync(fooFile));
302 303
		});

304
		test('untitled file', async () => {
305
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
306
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
B
Benjamin Pasero 已提交
307
			await service.discardBackup(untitledFile);
308 309
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
310 311
		});
	});
B
Benjamin Pasero 已提交
312

313 314
	suite('discardBackups', () => {
		test('text file', async () => {
315
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
316
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
317
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
318 319 320 321 322 323 324 325
			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 () => {
326
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
327 328 329 330 331 332 333 334
			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();
335
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
336 337 338 339
			assert.equal(fs.existsSync(workspaceBackupPath), true);
		});
	});

340
	suite('getBackups', () => {
341
		test('("file") - text file', async () => {
342
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
B
Benjamin Pasero 已提交
343
			const textFiles = await service.getBackups();
344
			assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
345
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
B
Benjamin Pasero 已提交
346
			const textFiles_1 = await service.getBackups();
347
			assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
D
Daniel Imms 已提交
348 349
		});

350
		test('("file") - untitled file', async () => {
351
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
B
Benjamin Pasero 已提交
352
			const textFiles = await service.getBackups();
353
			assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
D
Daniel Imms 已提交
354 355
		});

356
		test('("untitled") - untitled file', async () => {
357
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
B
Benjamin Pasero 已提交
358
			const textFiles = await service.getBackups();
359
			assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
360 361 362
		});
	});

363
	suite('resolve', () => {
364 365 366 367 368 369 370 371

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

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

375 376 377 378 379 380 381 382 383 384 385 386
			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
			};
387

388
			await testResolveBackup(untitledFile, contents, meta);
389 390
		});

391
		test('should restore the original contents (text file)', async () => {
392 393 394 395
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
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 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
				'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 ',
457
				'adipiscing ßß elit',
458
				'consectetur '
459 460
			].join('');

461 462 463 464 465 466
			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};
467

468
			await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta);
469 470 471 472 473 474 475 476

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

477 478
			const backup = await service.resolve(fooFile);
			assert.ok(backup);
479
			assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true)));
480
			assert.ok(!backup!.meta);
481 482
		});

483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
		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);
		});

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
		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 已提交
517
		});
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536

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

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

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

542
			const backup = await service.resolve(fooBarFile);
B
Benjamin Pasero 已提交
543 544 545 546
			if (!backup) {
				throw new Error('Unexpected missing backup');
			}

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

549
			let err: Error | undefined = undefined;
B
Benjamin Pasero 已提交
550
			try {
551
				await service.resolve<IBackupTestMetaData>(fooBarFile);
B
Benjamin Pasero 已提交
552 553 554 555
			} catch (error) {
				err = error;
			}

556
			assert.ok(!err);
B
Benjamin Pasero 已提交
557 558
		});

B
Benjamin Pasero 已提交
559
		async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) {
560 561 562 563
			if (typeof expectedMeta === 'undefined') {
				expectedMeta = meta;
			}

564
			await service.backup(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta);
565

566 567
			const backup = await service.resolve<IBackupTestMetaData>(resource);
			assert.ok(backup);
568
			assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true)));
569 570

			if (expectedMeta) {
571 572 573 574
				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);
575
			} else {
576
				assert.ok(!backup!.meta);
577 578
			}
		}
D
Daniel Imms 已提交
579
	});
D
Daniel Imms 已提交
580
});
D
Daniel Imms 已提交
581

D
Daniel Imms 已提交
582
suite('BackupFilesModel', () => {
583

584
	let service: NodeTestBackupFileService;
585 586

	setup(async () => {
587
		service = new NodeTestBackupFileService(workspaceBackupPath);
588 589

		// Delete any existing backups completely and then re-create it.
590
		await pfs.rimraf(backupHome);
591 592 593 594 595 596
		await pfs.mkdirp(backupHome);

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

	teardown(() => {
597
		return pfs.rimraf(backupHome);
598 599
	});

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

B
Benjamin Pasero 已提交
603
		const resource1 = URI.file('test.html');
B
Benjamin Pasero 已提交
604 605 606 607 608 609 610 611

		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);
612
		assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
B
Benjamin Pasero 已提交
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633

		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 已提交
634 635 636
		const resource2 = URI.file('test1.html');
		const resource3 = URI.file('test2.html');
		const resource4 = URI.file('test3.html');
B
Benjamin Pasero 已提交
637 638 639

		model.add(resource2);
		model.add(resource3);
640
		model.add(resource4, undefined, { foo: 'bar' });
B
Benjamin Pasero 已提交
641 642 643 644

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

B
Benjamin Pasero 已提交
646
		assert.equal(model.has(resource4), true);
647 648
		assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true);
		assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false);
B
Benjamin Pasero 已提交
649 650
	});

651 652 653
	test('resolve', async () => {
		await pfs.mkdirp(path.dirname(fooBackupPath));
		fs.writeFileSync(fooBackupPath, 'foo');
654
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
655

B
Benjamin Pasero 已提交
656 657
		const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
		assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true);
B
Benjamin Pasero 已提交
658
	});
659

D
Daniel Imms 已提交
660
	test('get', () => {
661
		const model = new BackupFilesModel(service.fileService);
662

663
		assert.deepEqual(model.get(), []);
664

B
Benjamin Pasero 已提交
665 666 667
		const file1 = URI.file('/root/file/foo.html');
		const file2 = URI.file('/root/file/bar.html');
		const untitled = URI.file('/root/untitled/bar.html');
668

D
Daniel Imms 已提交
669 670 671 672
		model.add(file1);
		model.add(file2);
		model.add(untitled);

673
		assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
674
	});
675
});