Skip to content

Commit b33801c

Browse files
committed
file working copy - first cut tests
1 parent 8736be1 commit b33801c

File tree

4 files changed

+290
-5
lines changed

4 files changed

+290
-5
lines changed

src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ILogService } from 'vs/platform/log/common/log';
2222
* operations that are working copy related, such as save/revert, backup
2323
* and resolving.
2424
*/
25-
export interface IFileWorkingCopyManager<T extends IFileWorkingCopyModel> {
25+
export interface IFileWorkingCopyManager<T extends IFileWorkingCopyModel> extends IDisposable {
2626

2727
/**
2828
* An event for when a file working copy was created.
@@ -334,7 +334,7 @@ export class FileWorkingCopyManager<T extends IFileWorkingCopyModel> extends Dis
334334
this.mapResourceToWorkingCopyListeners.set(workingCopy.resource, workingCopyListeners);
335335
}
336336

337-
protected add(resource: URI, workingCopy: IFileWorkingCopy<T>): void {
337+
private add(resource: URI, workingCopy: IFileWorkingCopy<T>): void {
338338
const knownWorkingCopy = this.mapResourceToWorkingCopy.get(resource);
339339
if (knownWorkingCopy === workingCopy) {
340340
return; // already cached
@@ -351,7 +351,7 @@ export class FileWorkingCopyManager<T extends IFileWorkingCopyModel> extends Dis
351351
this.mapResourceToDisposeListener.set(resource, workingCopy.onWillDispose(() => this.remove(resource)));
352352
}
353353

354-
protected remove(resource: URI): void {
354+
private remove(resource: URI): void {
355355
this.mapResourceToWorkingCopy.delete(resource);
356356

357357
const disposeListener = this.mapResourceToDisposeListener.get(resource);
@@ -367,7 +367,7 @@ export class FileWorkingCopyManager<T extends IFileWorkingCopyModel> extends Dis
367367
}
368368
}
369369

370-
clear(): void {
370+
private clear(): void {
371371

372372
// working copy caches
373373
this.mapResourceToWorkingCopy.clear();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { Emitter } from 'vs/base/common/event';
7+
import { URI } from 'vs/base/common/uri';
8+
import { IFileWorkingCopyModel, IFileWorkingCopyModelContentChangedEvent, IFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
9+
import { newWriteableBufferStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer';
10+
import { CancellationToken } from 'vs/base/common/cancellation';
11+
import { Disposable } from 'vs/base/common/lifecycle';
12+
13+
export class TestFileWorkingCopyModel extends Disposable implements IFileWorkingCopyModel {
14+
15+
private readonly _onDidChangeContent = this._register(new Emitter<IFileWorkingCopyModelContentChangedEvent>());
16+
readonly onDidChangeContent = this._onDidChangeContent.event;
17+
18+
private readonly _onWillDispose = this._register(new Emitter<void>());
19+
readonly onWillDispose = this._onWillDispose.event;
20+
21+
constructor(readonly resource: URI, public contents: string) {
22+
super();
23+
}
24+
25+
fireContentChangeEvent(event: IFileWorkingCopyModelContentChangedEvent): void {
26+
this._onDidChangeContent.fire(event);
27+
}
28+
29+
updateContents(newContents: string): void {
30+
this.contents = newContents;
31+
this.versionId++;
32+
33+
this._onDidChangeContent.fire({ isRedoing: false, isUndoing: false });
34+
}
35+
36+
async snapshot(token: CancellationToken): Promise<VSBufferReadableStream> {
37+
const stream = newWriteableBufferStream();
38+
stream.end(VSBuffer.fromString(this.contents));
39+
40+
return stream;
41+
}
42+
43+
async update(contents: VSBufferReadableStream, token: CancellationToken): Promise<void> {
44+
this.contents = (await streamToBuffer(contents)).toString();
45+
}
46+
47+
versionId = 0;
48+
49+
getAlternativeVersionId(): number {
50+
return this.versionId;
51+
}
52+
53+
pushStackElement(): void { }
54+
55+
dispose(): void {
56+
this._onWillDispose.fire();
57+
58+
super.dispose();
59+
}
60+
}
61+
62+
export class TestFileWorkingCopyModelFactory implements IFileWorkingCopyModelFactory<TestFileWorkingCopyModel> {
63+
64+
async createModel(resource: URI, contents: VSBufferReadableStream, token: CancellationToken): Promise<TestFileWorkingCopyModel> {
65+
return new TestFileWorkingCopyModel(resource, (await streamToBuffer(contents)).toString());
66+
}
67+
}
68+
69+
suite('FileWorkingCopy', () => {
70+
71+
72+
});
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import { URI } from 'vs/base/common/uri';
8+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
9+
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
10+
import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
11+
import { IFileWorkingCopy, IFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
12+
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
13+
import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
14+
import { timeout } from 'vs/base/common/async';
15+
import { TestFileWorkingCopyModel, TestFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test';
16+
17+
suite('FileWorkingCopyManager', () => {
18+
19+
let instantiationService: IInstantiationService;
20+
let accessor: TestServiceAccessor;
21+
22+
let manager: IFileWorkingCopyManager<TestFileWorkingCopyModel>;
23+
24+
setup(() => {
25+
instantiationService = workbenchInstantiationService();
26+
accessor = instantiationService.createInstance(TestServiceAccessor);
27+
28+
const factory = new TestFileWorkingCopyModelFactory();
29+
manager = new FileWorkingCopyManager<TestFileWorkingCopyModel>(factory, accessor.fileService, accessor.lifecycleService, accessor.labelService, instantiationService, accessor.logService);
30+
});
31+
32+
teardown(() => {
33+
manager.dispose();
34+
});
35+
36+
test('resolve', async () => {
37+
const resource = URI.file('/test.html');
38+
39+
const events: IFileWorkingCopy<IFileWorkingCopyModel>[] = [];
40+
const listener = manager.onDidCreate(workingCopy => {
41+
events.push(workingCopy);
42+
});
43+
44+
const resolvePromise = manager.resolve(resource);
45+
assert.ok(manager.get(resource)); // working copy known even before resolved()
46+
47+
const workingCopy1 = await resolvePromise;
48+
assert.ok(workingCopy1);
49+
assert.ok(workingCopy1.model);
50+
assert.strictEqual(manager.get(resource), workingCopy1);
51+
52+
const workingCopy2 = await manager.resolve(resource);
53+
assert.strictEqual(workingCopy2, workingCopy1);
54+
workingCopy1.dispose();
55+
56+
const workingCopy3 = await manager.resolve(resource);
57+
assert.notStrictEqual(workingCopy3, workingCopy2);
58+
assert.strictEqual(manager.get(resource), workingCopy3);
59+
workingCopy3.dispose();
60+
61+
assert.strictEqual(events.length, 2);
62+
assert.strictEqual(events[0].resource.toString(), workingCopy1.resource.toString());
63+
assert.strictEqual(events[1].resource.toString(), workingCopy2.resource.toString());
64+
65+
listener.dispose();
66+
67+
workingCopy1.dispose();
68+
workingCopy2.dispose();
69+
workingCopy3.dispose();
70+
});
71+
72+
test('resolve with initial contents', async () => {
73+
const resource = URI.file('/test.html');
74+
75+
const workingCopy = await manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('Hello World')) });
76+
assert.strictEqual(workingCopy.model?.contents, 'Hello World');
77+
assert.strictEqual(workingCopy.isDirty(), true);
78+
79+
await manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('More Changes')) });
80+
assert.strictEqual(workingCopy.model?.contents, 'More Changes');
81+
assert.strictEqual(workingCopy.isDirty(), true);
82+
83+
workingCopy.dispose();
84+
});
85+
86+
test('multiple resolves execute in sequence', async () => {
87+
const resource = URI.file('/test.html');
88+
89+
const firstPromise = manager.resolve(resource);
90+
const secondPromise = manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('Hello World')) });
91+
const thirdPromise = manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('More Changes')) });
92+
93+
await firstPromise;
94+
await secondPromise;
95+
const workingCopy = await thirdPromise;
96+
97+
assert.strictEqual(workingCopy.model?.contents, 'More Changes');
98+
assert.strictEqual(workingCopy.isDirty(), true);
99+
100+
workingCopy.dispose();
101+
});
102+
103+
test('removed from cache when working copy or model gets disposed', async () => {
104+
const resource = URI.file('/test.html');
105+
106+
let workingCopy = await manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('Hello World')) });
107+
108+
assert.strictEqual(manager.get(URI.file('/test.html')), workingCopy);
109+
110+
workingCopy.dispose();
111+
assert(!manager.get(URI.file('/test.html')));
112+
113+
workingCopy = await manager.resolve(resource, { contents: bufferToStream(VSBuffer.fromString('Hello World')) });
114+
115+
assert.strictEqual(manager.get(URI.file('/test.html')), workingCopy);
116+
117+
workingCopy.model?.dispose();
118+
assert(!manager.get(URI.file('/test.html')));
119+
});
120+
121+
test('events', async () => {
122+
const resource1 = URI.file('/path/index.txt');
123+
const resource2 = URI.file('/path/other.txt');
124+
125+
let loadedCounter = 0;
126+
let gotDirtyCounter = 0;
127+
let gotNonDirtyCounter = 0;
128+
let revertedCounter = 0;
129+
let savedCounter = 0;
130+
131+
manager.onDidResolve(workingCopy => {
132+
if (workingCopy.resource.toString() === resource1.toString()) {
133+
loadedCounter++;
134+
}
135+
});
136+
137+
manager.onDidChangeDirty(workingCopy => {
138+
if (workingCopy.resource.toString() === resource1.toString()) {
139+
if (workingCopy.isDirty()) {
140+
gotDirtyCounter++;
141+
} else {
142+
gotNonDirtyCounter++;
143+
}
144+
}
145+
});
146+
147+
manager.onDidRevert(workingCopy => {
148+
if (workingCopy.resource.toString() === resource1.toString()) {
149+
revertedCounter++;
150+
}
151+
});
152+
153+
manager.onDidSave(({ workingCopy }) => {
154+
if (workingCopy.resource.toString() === resource1.toString()) {
155+
savedCounter++;
156+
}
157+
});
158+
159+
const workingCopy1 = await manager.resolve(resource1);
160+
assert.strictEqual(loadedCounter, 1);
161+
162+
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }], false));
163+
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }], false));
164+
165+
const workingCopy2 = await manager.resolve(resource2);
166+
assert.strictEqual(loadedCounter, 2);
167+
168+
workingCopy1.model?.updateContents('changed');
169+
170+
await workingCopy1.revert();
171+
workingCopy1.model?.updateContents('changed again');
172+
173+
await workingCopy1.save();
174+
workingCopy1.dispose();
175+
workingCopy2.dispose();
176+
177+
await workingCopy1.revert();
178+
assert.strictEqual(gotDirtyCounter, 2);
179+
assert.strictEqual(gotNonDirtyCounter, 2);
180+
assert.strictEqual(revertedCounter, 1);
181+
assert.strictEqual(savedCounter, 1);
182+
183+
workingCopy1.dispose();
184+
workingCopy2.dispose();
185+
});
186+
187+
test('canDispose with dirty model', async function () {
188+
const resource = URI.file('/path/index_something.txt');
189+
190+
const workingCopy = await manager.resolve(resource);
191+
workingCopy.model?.updateContents('make dirty');
192+
193+
let canDisposePromise = manager.canDispose(workingCopy);
194+
assert.ok(canDisposePromise instanceof Promise);
195+
196+
let canDispose = false;
197+
(async () => {
198+
canDispose = await canDisposePromise;
199+
})();
200+
201+
assert.strictEqual(canDispose, false);
202+
workingCopy.revert({ soft: true });
203+
204+
await timeout(0);
205+
206+
assert.strictEqual(canDispose, true);
207+
208+
let canDispose2 = manager.canDispose(workingCopy);
209+
assert.strictEqual(canDispose2, true);
210+
});
211+
});

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ export class TestServiceAccessor {
256256
@IConfigurationService public testConfigurationService: TestConfigurationService,
257257
@IBackupFileService public backupFileService: TestBackupFileService,
258258
@IHostService public hostService: TestHostService,
259-
@IQuickInputService public quickInputService: IQuickInputService
259+
@IQuickInputService public quickInputService: IQuickInputService,
260+
@ILabelService public labelService: ILabelService,
261+
@ILogService public logService: ILogService
260262
) { }
261263
}
262264

0 commit comments

Comments
 (0)