backupMainService.test.ts 15.2 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
D
Daniel Imms 已提交
10
import fs = require('fs');
11 12 13 14 15
import os = require('os');
import path = require('path');
import extfs = require('vs/base/node/extfs');
import pfs = require('vs/base/node/pfs');
import Uri from 'vs/base/common/uri';
16 17
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
D
Daniel Imms 已提交
18
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
19
import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup';
20 21
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
22

D
Daniel Imms 已提交
23
class TestBackupMainService extends BackupMainService {
24 25
	constructor(backupHome: string, backupWorkspacesPath: string, configService: TestConfigurationService) {
		super(new EnvironmentService(parseArgs(process.argv), process.execPath), configService);
26 27

		this.backupHome = backupHome;
D
Daniel Imms 已提交
28
		this.workspacesJsonPath = backupWorkspacesPath;
29 30 31

		// Force a reload with the new paths
		this.loadSync();
32
	}
33

34 35
	public removeBackupPathSync(workspaceIdenfitier: string, isEmptyWorkspace: boolean): void {
		return super.removeBackupPathSync(workspaceIdenfitier, isEmptyWorkspace);
36 37 38 39 40 41
	}

	public loadSync(): void {
		super.loadSync();
	}

42 43
	public dedupeFolderWorkspaces(backups: IBackupWorkspacesFormat): IBackupWorkspacesFormat {
		return super.dedupeFolderWorkspaces(backups);
D
Daniel Imms 已提交
44 45
	}

46
	public toBackupPath(workspacePath: string): string {
D
Daniel Imms 已提交
47
		return path.join(this.backupHome, super.getWorkspaceHash(workspacePath));
48
	}
49 50 51 52

	public getWorkspaceHash(workspacePath: string): string {
		return super.getWorkspaceHash(workspacePath);
	}
53 54
}

D
Daniel Imms 已提交
55
suite('BackupMainService', () => {
D
Daniel Imms 已提交
56
	const parentDir = path.join(os.tmpdir(), 'vsctests', 'service');
57 58 59 60 61 62
	const backupHome = path.join(parentDir, 'Backups');
	const backupWorkspacesPath = path.join(backupHome, 'workspaces.json');

	const fooFile = Uri.file(platform.isWindows ? 'C:\\foo' : '/foo');
	const barFile = Uri.file(platform.isWindows ? 'C:\\bar' : '/bar');

63
	let service: TestBackupMainService;
64
	let configService: TestConfigurationService;
65 66

	setup(done => {
67 68
		configService = new TestConfigurationService();
		service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
69 70 71 72

		// Delete any existing backups completely and then re-create it.
		extfs.del(backupHome, os.tmpdir(), () => {
			pfs.mkdirp(backupHome).then(() => {
D
Daniel Imms 已提交
73
				done();
74 75 76 77 78 79 80 81
			});
		});
	});

	teardown(done => {
		extfs.del(backupHome, os.tmpdir(), done);
	});

82
	test('service validates backup workspaces on startup and cleans up', done => {
83

84
		// 1) backup workspace path does not exist
B
Benjamin Pasero 已提交
85 86
		service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
		service.registerWindowForBackupsSync(2, false, null, barFile.fsPath);
87
		service.loadSync();
88
		assert.deepEqual(service.getWorkspaceBackupPaths(), []);
89 90 91 92

		// 2) backup workspace path exists with empty contents within
		fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
		fs.mkdirSync(service.toBackupPath(barFile.fsPath));
B
Benjamin Pasero 已提交
93 94
		service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
		service.registerWindowForBackupsSync(2, false, null, barFile.fsPath);
95
		service.loadSync();
96
		assert.deepEqual(service.getWorkspaceBackupPaths(), []);
97 98 99 100 101 102 103 104
		assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
		assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));

		// 3) backup workspace path exists with empty folders within
		fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
		fs.mkdirSync(service.toBackupPath(barFile.fsPath));
		fs.mkdirSync(path.join(service.toBackupPath(fooFile.fsPath), 'file'));
		fs.mkdirSync(path.join(service.toBackupPath(barFile.fsPath), 'untitled'));
B
Benjamin Pasero 已提交
105 106
		service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
		service.registerWindowForBackupsSync(2, false, null, barFile.fsPath);
107
		service.loadSync();
108
		assert.deepEqual(service.getWorkspaceBackupPaths(), []);
109 110 111
		assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
		assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));

112 113 114 115 116 117
		// 4) backup workspace path points to a workspace that no longer exists
		// so it should convert the backup worspace to an empty workspace backup
		const fileBackups = path.join(service.toBackupPath(fooFile.fsPath), 'file');
		fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
		fs.mkdirSync(service.toBackupPath(barFile.fsPath));
		fs.mkdirSync(fileBackups);
B
Benjamin Pasero 已提交
118
		service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
119
		assert.equal(service.getWorkspaceBackupPaths().length, 1);
120
		assert.equal(service.getEmptyWindowBackupPaths().length, 0);
121 122
		fs.writeFileSync(path.join(fileBackups, 'backup.txt'), '');
		service.loadSync();
123
		assert.equal(service.getWorkspaceBackupPaths().length, 0);
124
		assert.equal(service.getEmptyWindowBackupPaths().length, 1);
125

126 127 128
		done();
	});

129
	suite('loadSync', () => {
130 131
		test('getWorkspaceBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
132
		});
D
Daniel Imms 已提交
133

134
		test('getWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', () => {
135 136
			fs.writeFileSync(backupWorkspacesPath, '');
			service.loadSync();
137
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
138 139
			fs.writeFileSync(backupWorkspacesPath, '{]');
			service.loadSync();
140
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
141 142
			fs.writeFileSync(backupWorkspacesPath, 'foo');
			service.loadSync();
143
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
144
		});
D
Daniel Imms 已提交
145

146
		test('getWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', () => {
147 148
			fs.writeFileSync(backupWorkspacesPath, '{}');
			service.loadSync();
149
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
150 151
		});

152
		test('getWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
153 154
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{}}');
			service.loadSync();
155
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
156 157
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": ["bar"]}}');
			service.loadSync();
158
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
159 160
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": []}}');
			service.loadSync();
161
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
162 163
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": "bar"}}');
			service.loadSync();
164
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
165 166
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":"foo"}');
			service.loadSync();
167
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
168 169
			fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":1}');
			service.loadSync();
170
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
171
		});
D
Daniel Imms 已提交
172

173 174 175 176 177 178 179 180
		test('getWorkspaceBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', () => {
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath.toUpperCase());
			assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath.toUpperCase()]);
			configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE);
			service.loadSync();
			assert.deepEqual(service.getWorkspaceBackupPaths(), []);
		});

181
		test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
182
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
183 184
		});

185
		test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', () => {
186 187
			fs.writeFileSync(backupWorkspacesPath, '');
			service.loadSync();
188
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
189 190
			fs.writeFileSync(backupWorkspacesPath, '{]');
			service.loadSync();
191
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
192 193
			fs.writeFileSync(backupWorkspacesPath, 'foo');
			service.loadSync();
194
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
195 196
		});

197
		test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', () => {
198 199
			fs.writeFileSync(backupWorkspacesPath, '{}');
			service.loadSync();
200
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
201 202
		});

203
		test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
204 205
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}');
			service.loadSync();
206
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
207 208
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}');
			service.loadSync();
209
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
210 211
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}');
			service.loadSync();
212
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
213 214
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}');
			service.loadSync();
215
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
216 217
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}');
			service.loadSync();
218
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
219 220
			fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}');
			service.loadSync();
221
			assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
222
		});
D
Daniel Imms 已提交
223
	});
224

225 226
	suite('dedupeFolderWorkspaces', () => {
		test('should ignore duplicates on Windows and Mac', () => {
227 228 229 230 231
			// Skip test on Linux
			if (platform.isLinux) {
				return;
			}

D
Daniel Imms 已提交
232
			const backups: IBackupWorkspacesFormat = {
233
				folderWorkspaces: platform.isWindows ? ['c:\\FOO', 'C:\\FOO', 'c:\\foo'] : ['/FOO', '/foo'],
D
Daniel Imms 已提交
234 235 236
				emptyWorkspaces: []
			};

237
			service.dedupeFolderWorkspaces(backups);
238

239
			assert.equal(backups.folderWorkspaces.length, 1);
240
			if (platform.isWindows) {
241
				assert.deepEqual(backups.folderWorkspaces, ['c:\\FOO'], 'should return the first duplicated entry');
D
Daniel Imms 已提交
242
			} else {
243
				assert.deepEqual(backups.folderWorkspaces, ['/FOO'], 'should return the first duplicated entry');
244 245
			}
		});
D
Daniel Imms 已提交
246 247
	});

248
	suite('registerWindowForBackups', () => {
D
Daniel Imms 已提交
249
		test('should persist paths to workspaces.json', done => {
B
Benjamin Pasero 已提交
250 251
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
			service.registerWindowForBackupsSync(2, false, null, barFile.fsPath);
252
			assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath, barFile.fsPath]);
253 254 255
			pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
				const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
				assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath, barFile.fsPath]);
D
Daniel Imms 已提交
256
				done();
257 258
			});
		});
D
Daniel Imms 已提交
259 260 261 262 263 264 265 266 267 268

		test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive', done => {
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath.toUpperCase());
			assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath.toUpperCase()]);
			pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
				const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
				assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath.toUpperCase()]);
				done();
			});
		});
269 270
	});

271
	suite('removeBackupPathSync', () => {
D
Daniel Imms 已提交
272
		test('should remove folder workspaces from workspaces.json', done => {
B
Benjamin Pasero 已提交
273 274
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
			service.registerWindowForBackupsSync(2, false, null, barFile.fsPath);
275
			service.removeBackupPathSync(fooFile.fsPath, false);
276 277 278
			pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
				const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
				assert.deepEqual(json.folderWorkspaces, [barFile.fsPath]);
279
				service.removeBackupPathSync(barFile.fsPath, false);
280 281 282 283 284
				pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
					const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
					assert.deepEqual(json2.folderWorkspaces, []);
					done();
				});
285 286
			});
		});
287

D
Daniel Imms 已提交
288
		test('should remove empty workspaces from workspaces.json', done => {
B
Benjamin Pasero 已提交
289 290
			service.registerWindowForBackupsSync(1, true, 'foo');
			service.registerWindowForBackupsSync(2, true, 'bar');
D
Daniel Imms 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303
			service.removeBackupPathSync('foo', true);
			pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
				const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
				assert.deepEqual(json.emptyWorkspaces, ['bar']);
				service.removeBackupPathSync('bar', true);
				pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
					const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
					assert.deepEqual(json2.emptyWorkspaces, []);
					done();
				});
			});
		});

304
		test('should fail gracefully when removing a path that doesn\'t exist', done => {
305 306
			const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath], emptyWorkspaces: [] };
			pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
307
				service.removeBackupPathSync(barFile.fsPath, false);
D
Daniel Imms 已提交
308
				service.removeBackupPathSync('test', true);
309 310 311 312 313 314 315
				pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
					const json = <IBackupWorkspacesFormat>JSON.parse(content);
					assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath]);
					done();
				});
			});
		});
316
	});
D
Daniel Imms 已提交
317

318
	suite('getWorkspaceHash', () => {
D
Daniel Imms 已提交
319 320 321 322
		test('should perform an md5 hash on the path', () => {
			assert.equal(service.getWorkspaceHash('/foo'), '1effb2475fcfba4f9e8b8a1dbc8f3caf');
		});

323 324 325 326 327
		test('should ignore case on Windows and Mac', () => {
			// Skip test on Linux
			if (platform.isLinux) {
				return;
			}
D
Daniel Imms 已提交
328

329 330 331
			if (platform.isMacintosh) {
				assert.equal(service.getWorkspaceHash('/foo'), service.getWorkspaceHash('/FOO'));
			}
D
Daniel Imms 已提交
332

333 334 335 336
			if (platform.isWindows) {
				assert.equal(service.getWorkspaceHash('c:\\foo'), service.getWorkspaceHash('C:\\FOO'));
			}
		});
D
Daniel Imms 已提交
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
	suite('mixed path casing', () => {
		test('should handle case insensitive paths properly (registerWindowForBackupsSync)', done => {
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath.toUpperCase());

			if (platform.isLinux) {
				assert.equal(service.getWorkspaceBackupPaths().length, 2);
			} else {
				assert.equal(service.getWorkspaceBackupPaths().length, 1);
			}

			done();
		});

		test('should handle case insensitive paths properly (removeBackupPathSync)', done => {

			// same case
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
			service.removeBackupPathSync(fooFile.fsPath, false);
			assert.equal(service.getWorkspaceBackupPaths().length, 0);

			// mixed case
			service.registerWindowForBackupsSync(1, false, null, fooFile.fsPath);
			service.removeBackupPathSync(fooFile.fsPath.toUpperCase(), false);

			if (platform.isLinux) {
				assert.equal(service.getWorkspaceBackupPaths().length, 1);
			} else {
				assert.equal(service.getWorkspaceBackupPaths().length, 0);
			}

			done();
		});
	});
373
});