Adding better test case for not caching cancelled responses

This commit is contained in:
Matt Bierner
2018-12-10 13:47:53 -08:00
parent 68e0182064
commit 2b7278c712
2 changed files with 48 additions and 35 deletions

View File

@@ -15,11 +15,10 @@ suite('CachedResponse', () => {
const doc = await vscode.workspace.openTextDocument({ language: 'javascript', content: '' });
const response = new CachedResponse();
let seq = 0;
const makeRequest = async () => createResponse(`test-${seq++}`);
const responder = createSequentialResponder('test');
assertResult(await response.execute(doc, makeRequest), 'test-0');
assertResult(await response.execute(doc, makeRequest), 'test-0');
assertResult(await response.execute(doc, responder), 'test-0');
assertResult(await response.execute(doc, responder), 'test-0');
});
test('should invalidate cache for new document', async () => {
@@ -27,29 +26,32 @@ suite('CachedResponse', () => {
const doc2 = await vscode.workspace.openTextDocument({ language: 'javascript', content: '' });
const response = new CachedResponse();
let seq = 0;
const makeRequest = async () => createResponse(`test-${seq++}`);
const responder = createSequentialResponder('test');
assertResult(await response.execute(doc1, makeRequest), 'test-0');
assertResult(await response.execute(doc1, makeRequest), 'test-0');
assertResult(await response.execute(doc2, makeRequest), 'test-1');
assertResult(await response.execute(doc2, makeRequest), 'test-1');
assertResult(await response.execute(doc1, makeRequest), 'test-2');
assertResult(await response.execute(doc1, makeRequest), 'test-2');
assertResult(await response.execute(doc1, responder), 'test-0');
assertResult(await response.execute(doc1, responder), 'test-0');
assertResult(await response.execute(doc2, responder), 'test-1');
assertResult(await response.execute(doc2, responder), 'test-1');
assertResult(await response.execute(doc1, responder), 'test-2');
assertResult(await response.execute(doc1, responder), 'test-2');
});
test('should not cache canceled response', async () => {
test('should not cache cancelled responses', async () => {
const doc = await vscode.workspace.openTextDocument({ language: 'javascript', content: '' });
const response = new CachedResponse();
let seq = 0;
const makeRequest = async () => createResponse(`test-${seq++}`);
const responder = createSequentialResponder('test');
const result1 = await response.execute(doc, async () => new CancelledResponse('cancleed'));
assert.strictEqual(result1.type, 'cancelled');
const cancelledResponder = createEventualResponder<CancelledResponse>();
const result1 = response.execute(doc, () => cancelledResponder.promise);
const result2 = response.execute(doc, responder);
const result3 = response.execute(doc, responder);
assertResult(await response.execute(doc, makeRequest), 'test-0');
assertResult(await response.execute(doc, makeRequest), 'test-0');
cancelledResponder.resolve(new CancelledResponse('cancelled'));
assert.strictEqual((await result1).type, 'cancelled');
assertResult(await result2, 'test-0');
assertResult(await result3, 'test-0');
});
});
@@ -72,3 +74,13 @@ function createResponse(command: string): Proto.Response {
};
}
function createSequentialResponder(prefix: string) {
let count = 0;
return async () => createResponse(`${prefix}-${count++}`);
}
function createEventualResponder<T>(): {promise: Promise<T>, resolve: (x: T) => void } {
let resolve: (value: T) => void;
const promise = new Promise<T>(r => { resolve = r; });
return { promise, resolve: resolve! };
}

View File

@@ -7,41 +7,42 @@ import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ServerResponse } from '../typescriptService';
type Resolve<T extends Proto.Response> = () => Promise<ServerResponse<T>>;
/**
* Caches a class of TS Server request based on document.
*/
export class CachedResponse<T extends Proto.Response> {
private response?: Promise<ServerResponse<T>>;
private version: number = -1;
private document: string = '';
/**
* Execute a request. May return cached value or resolve the new value
*
* Caller must ensure that all input `resolve` functions return equivilent results (keyed only off of document).
*/
public execute(
document: vscode.TextDocument,
f: () => Promise<ServerResponse<T>>
resolve: Resolve<T>
): Promise<ServerResponse<T>> {
if (this.response && this.matches(document)) {
return this.response.then(result => result.type === 'cancelled' ? f() : result);
// Chain so that on cancellation we fall back to the next resolve
return this.response = this.response.then(result => result.type === 'cancelled' ? resolve() : result);
}
return this.update(document, f());
return this.reset(document, resolve);
}
private matches(document: vscode.TextDocument): boolean {
return this.version === document.version && this.document === document.uri.toString();
}
private async update(
private async reset(
document: vscode.TextDocument,
response: Promise<ServerResponse<T>>
resolve: Resolve<T>
): Promise<ServerResponse<T>> {
this.response = response;
this.version = document.version;
this.document = document.uri.toString();
const result = await response;
if (this.matches(document)) {
if (result.type === 'cancelled') {
// invalidate
this.version = -1;
this.document = '';
}
}
return result;
return this.response = resolve();
}
}