backupFileService.test.ts 24.0 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
import { TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
31
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
32

33 34
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
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
const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
46
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
47
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
D
Daniel Imms 已提交
48

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

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

56
export class NodeTestBackupFileService extends BackupFileService {
57 58 59

	readonly fileService: IFileService;

60 61
	private backupResourceJoiners: Function[];
	private discardBackupJoiners: Function[];
62
	discardedBackups: URI[];
63

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

72
		super(environmentService, fileService, logService);
73 74

		this.fileService = fileService;
75 76
		this.backupResourceJoiners = [];
		this.discardBackupJoiners = [];
77
		this.discardedBackups = [];
78 79
	}

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

84 85
	async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any, token?: CancellationToken): Promise<void> {
		await super.backup(resource, content, versionId, meta, token);
86 87 88 89 90 91 92 93 94 95

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

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

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

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

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

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

		return fileContents.value.toString();
	}
112 113
}

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

117
	setup(async () => {
118
		service = new NodeTestBackupFileService(workspaceBackupPath);
119

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

238
			model.dispose();
239 240
		});

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

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

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

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

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

264
			model.dispose();
265
		});
266 267 268 269 270 271 272 273 274 275

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

278
	suite('discardBackup', () => {
279
		test('text file', async () => {
B
Benjamin Pasero 已提交
280
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
281
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
282 283
			assert.ok(service.hasBackupSync(fooFile));

B
Benjamin Pasero 已提交
284
			await service.discardBackup(fooFile);
285 286
			assert.equal(fs.existsSync(fooBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
287
			assert.ok(!service.hasBackupSync(fooFile));
288 289
		});

290
		test('untitled file', async () => {
B
Benjamin Pasero 已提交
291
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
292
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
B
Benjamin Pasero 已提交
293
			await service.discardBackup(untitledFile);
294 295
			assert.equal(fs.existsSync(untitledBackupPath), false);
			assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
296 297
		});
	});
B
Benjamin Pasero 已提交
298

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
	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);
		});
	});

326
	suite('getBackups', () => {
327
		test('("file") - text file', async () => {
B
Benjamin Pasero 已提交
328 329
			await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
330
			assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
B
Benjamin Pasero 已提交
331 332
			await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles_1 = await service.getBackups();
333
			assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
D
Daniel Imms 已提交
334 335
		});

336
		test('("file") - untitled file', async () => {
B
Benjamin Pasero 已提交
337 338
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
339
			assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
D
Daniel Imms 已提交
340 341
		});

342
		test('("untitled") - untitled file', async () => {
B
Benjamin Pasero 已提交
343 344
			await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
			const textFiles = await service.getBackups();
345
			assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
346 347 348
		});
	});

349
	suite('resolve', () => {
350 351 352 353 354 355 356 357

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

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

361 362 363 364 365 366 367 368 369 370 371 372
			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
			};
373

374
			await testResolveBackup(untitledFile, contents, meta);
375 376
		});

377
		test('should restore the original contents (text file)', async () => {
378 379 380 381
			const contents = [
				'Lorem ipsum ',
				'dolor öäü sit amet ',
				'consectetur ',
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 431 432 433 434 435 436 437 438 439 440 441 442
				'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 ',
443
				'adipiscing ßß elit',
444
				'consectetur '
445 446
			].join('');

447 448 449 450 451 452
			const meta = {
				etag: 'theEtag',
				size: 888,
				mtime: Date.now(),
				orphaned: false
			};
453

B
Benjamin Pasero 已提交
454
			await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
455 456 457 458 459 460 461 462

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

463 464 465 466
			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);
467 468
		});

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
		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);
		});

487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
		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 已提交
503
		});
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522

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

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

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

528
			const backup = await service.resolve(fooBarFile);
B
Benjamin Pasero 已提交
529 530 531 532
			if (!backup) {
				throw new Error('Unexpected missing backup');
			}

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

535
			let err: Error | undefined = undefined;
B
Benjamin Pasero 已提交
536
			try {
537
				await service.resolve<IBackupTestMetaData>(fooBarFile);
B
Benjamin Pasero 已提交
538 539 540 541
			} catch (error) {
				err = error;
			}

542
			assert.ok(!err);
B
Benjamin Pasero 已提交
543 544
		});

B
Benjamin Pasero 已提交
545
		async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) {
546 547 548 549
			if (typeof expectedMeta === 'undefined') {
				expectedMeta = meta;
			}

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

552 553 554
			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)));
555 556

			if (expectedMeta) {
557 558 559 560
				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);
561
			} else {
562
				assert.ok(!backup!.meta);
563 564
			}
		}
D
Daniel Imms 已提交
565
	});
D
Daniel Imms 已提交
566
});
D
Daniel Imms 已提交
567

D
Daniel Imms 已提交
568
suite('BackupFilesModel', () => {
569

570
	let service: NodeTestBackupFileService;
571 572

	setup(async () => {
573
		service = new NodeTestBackupFileService(workspaceBackupPath);
574 575 576 577 578 579 580 581 582 583 584 585

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

B
Benjamin Pasero 已提交
589
		const resource1 = URI.file('test.html');
B
Benjamin Pasero 已提交
590 591 592 593 594 595 596 597

		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);
598
		assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
B
Benjamin Pasero 已提交
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619

		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 已提交
620 621 622
		const resource2 = URI.file('test1.html');
		const resource3 = URI.file('test2.html');
		const resource4 = URI.file('test3.html');
B
Benjamin Pasero 已提交
623 624 625

		model.add(resource2);
		model.add(resource3);
626
		model.add(resource4, undefined, { foo: 'bar' });
B
Benjamin Pasero 已提交
627 628 629 630

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

B
Benjamin Pasero 已提交
632
		assert.equal(model.has(resource4), true);
633 634
		assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true);
		assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false);
B
Benjamin Pasero 已提交
635 636
	});

637 638 639
	test('resolve', async () => {
		await pfs.mkdirp(path.dirname(fooBackupPath));
		fs.writeFileSync(fooBackupPath, 'foo');
640
		const model = new BackupFilesModel(service.fileService);
B
Benjamin Pasero 已提交
641

B
Benjamin Pasero 已提交
642 643
		const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
		assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true);
B
Benjamin Pasero 已提交
644
	});
645

D
Daniel Imms 已提交
646
	test('get', () => {
647
		const model = new BackupFilesModel(service.fileService);
648

649
		assert.deepEqual(model.get(), []);
650

B
Benjamin Pasero 已提交
651 652 653
		const file1 = URI.file('/root/file/foo.html');
		const file2 = URI.file('/root/file/bar.html');
		const untitled = URI.file('/root/untitled/bar.html');
654

D
Daniel Imms 已提交
655 656 657 658
		model.add(file1);
		model.add(file2);
		model.add(untitled);

659
		assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
660
	});
661
});