Merge pull request #139234 from microsoft/tyriar/120077

Use async/await in env var tests and improve polling
This commit is contained in:
Daniel Imms 2021-12-16 11:34:27 -08:00 committed by GitHub
commit 927096c078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 103 deletions

View File

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deepEqual, deepStrictEqual, doesNotThrow, equal, strictEqual, throws } from 'assert';
import { deepStrictEqual, doesNotThrow, equal, strictEqual, throws } from 'assert';
import { ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalOptions, TerminalState, UIKind, window, workspace } from 'vscode';
import { assertNoRpc } from '../utils';
import { assertNoRpc, poll } from '../utils';
// Disable terminal tests:
// - Web https://github.com/microsoft/vscode/issues/92826
@ -220,7 +220,7 @@ import { assertNoRpc } from '../utils';
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(t => {
if (t === terminal) {
deepEqual(t.exitStatus, { code: undefined });
deepStrictEqual(t.exitStatus, { code: undefined });
r();
}
}));
@ -364,13 +364,13 @@ import { assertNoRpc } from '../utils';
closeEvents.push(e.name);
try {
if (closeEvents.length === 1) {
deepEqual(openEvents, ['test1']);
deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }]);
deepEqual(closeEvents, ['test1']);
deepStrictEqual(openEvents, ['test1']);
deepStrictEqual(dataEvents, [{ name: 'test1', data: 'write1' }]);
deepStrictEqual(closeEvents, ['test1']);
} else if (closeEvents.length === 2) {
deepEqual(openEvents, ['test1', 'test2']);
deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' }]);
deepEqual(closeEvents, ['test1', 'test2']);
deepStrictEqual(openEvents, ['test1', 'test2']);
deepStrictEqual(dataEvents, [{ name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' }]);
deepStrictEqual(closeEvents, ['test1', 'test2']);
}
resolveOnceClosed!();
} catch (e) {
@ -667,29 +667,8 @@ import { assertNoRpc } from '../utils';
});
suite('environmentVariableCollection', () => {
test.skip('should have collection variables apply to terminals immediately after setting', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
'b1~b2~',
'~c2~c1'
];
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
test('should have collection variables apply to terminals immediately after setting', async () => {
// Setup collection and create terminal
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
@ -702,6 +681,16 @@ import { assertNoRpc } from '../utils';
C: 'c1'
}
});
// Listen for all data events
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
}));
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
@ -710,31 +699,21 @@ import { assertNoRpc } from '../utils';
terminal.sendText('echo $B');
terminal.sendText('$env:C');
terminal.sendText('echo $C');
// Poll for the echo results to show up
await poll<void>(() => Promise.resolve(), () => data.includes('~a2~'), '~a2~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('b1~b2~'), 'b1~b2~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('~c2~c1'), '~c2~c1 should be printed');
// Wait for terminal to be disposed
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(() => r()));
terminal.dispose();
});
});
test('should have collection variables apply to environment variables that don\'t exist', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
'~b2~',
'~c2~'
];
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
test('should have collection variables apply to environment variables that don\'t exist', async () => {
// Setup collection and create terminal
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
@ -747,6 +726,16 @@ import { assertNoRpc } from '../utils';
C: null
}
});
// Listen for all data events
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
}));
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
@ -755,30 +744,21 @@ import { assertNoRpc } from '../utils';
terminal.sendText('echo $B');
terminal.sendText('$env:C');
terminal.sendText('echo $C');
// Poll for the echo results to show up
await poll<void>(() => Promise.resolve(), () => data.includes('~a2~'), '~a2~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('~b2~'), '~b2~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('~c2~'), '~c2~ should be printed');
// Wait for terminal to be disposed
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(() => r()));
terminal.dispose();
});
});
test.skip('should respect clearing entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
'~b1~'
];
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
test('should respect clearing entries', async () => {
// Setup collection and create terminal
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
@ -790,36 +770,36 @@ import { assertNoRpc } from '../utils';
B: '~b1~'
}
});
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
});
test('should respect deleting entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
'~b2~'
];
// Listen for all data events
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
// Poll for the echo results to show up
await poll<void>(() => Promise.resolve(), () => data.includes('~a1~'), '~a1~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('~b1~'), '~b1~ should be printed');
// Wait for terminal to be disposed
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(() => r()));
terminal.dispose();
});
});
test('should respect deleting entries', async () => {
// Setup collection and create terminal
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
@ -831,12 +811,32 @@ import { assertNoRpc } from '../utils';
B: '~b2~'
}
});
// Listen for all data events
let data = '';
disposables.push(window.onDidWriteTerminalData(e => {
if (terminal !== e.terminal) {
return;
}
data += sanitizeData(e.data);
}));
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
// Poll for the echo results to show up
await poll<void>(() => Promise.resolve(), () => data.includes('~a1~'), '~a1~ should be printed');
await poll<void>(() => Promise.resolve(), () => data.includes('~b2~'), '~b2~ should be printed');
// Wait for terminal to be disposed
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(() => r()));
terminal.dispose();
});
});
test('get and forEach should work', () => {
@ -847,14 +847,14 @@ import { assertNoRpc } from '../utils';
collection.prepend('C', '~c2~');
// Verify get
deepEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace });
deepEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append });
deepEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend });
deepStrictEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace });
deepStrictEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append });
deepStrictEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend });
// Verify forEach
const entries: [string, EnvironmentVariableMutator][] = [];
collection.forEach((v, m) => entries.push([v, m]));
deepEqual(entries, [
deepStrictEqual(entries, [
['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append }],
['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend }]

View File

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { EOL } from 'os';
import * as crypto from 'crypto';
import * as vscode from 'vscode';
import { TestFS } from './memfs';
@ -150,3 +151,35 @@ export function suiteRepeat(n: number, description: string, callback: (this: any
suite(`${description} (iteration ${i})`, callback);
}
}
export async function poll<T>(
fn: () => Thenable<T>,
acceptFn: (result: T) => boolean,
timeoutMessage: string,
retryCount: number = 200,
retryInterval: number = 100 // millis
): Promise<T> {
let trial = 1;
let lastError: string = '';
while (true) {
if (trial > retryCount) {
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.\r${lastError}`);
}
let result;
try {
result = await fn();
if (acceptFn(result)) {
return result;
} else {
lastError = 'Did not pass accept function';
}
} catch (e: any) {
lastError = Array.isArray(e.stack) ? e.stack.join(EOL) : e.stack;
}
await new Promise(resolve => setTimeout(resolve, retryInterval));
trial++;
}
}