Skip to content

Commit d2ceec7

Browse files
committed
Get windows tests to be able to pass (#9224)
* Fix tests to run again on windows * Fix mimeTypes test too * Fix the other failing mimetype * Fix linter * Fix export tests too * Fix unit tests * Fix the rest of the functional tests to pass too * Fix linter * Remove stub to fix other tests
1 parent 6846e5b commit d2ceec7

9 files changed

Lines changed: 95 additions & 79 deletions

File tree

src/client/common/process/pythonDaemonPool.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
4141
private readonly activatedEnvVariables?: NodeJS.ProcessEnv,
4242
private readonly timeoutWaitingForDaemon: number = 1_000
4343
) {
44-
if (!options.pythonPath){
44+
if (!options.pythonPath) {
4545
throw new Error('options.pythonPath is empty when it shoud not be');
4646
}
4747
this.pythonPath = options.pythonPath;
4848
// Setup environment variables for the daemon.
4949
// The daemon must have access to the Python Module that'll run the daemon
5050
// & also access to a Python package used for the JSON rpc comms.
5151
const envPythonPath = `${path.join(EXTENSION_ROOT_DIR, 'pythonFiles')}${path.delimiter}${path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python')}`;
52-
this.envVariables = this.activatedEnvVariables ? { ...this.activatedEnvVariables } : {};
52+
this.envVariables = this.activatedEnvVariables ? { ...this.activatedEnvVariables } : { ...process.env };
5353
this.envVariables.PYTHONPATH = this.envVariables.PYTHONPATH ? `${this.envVariables.PYTHONPATH}${path.delimiter}${envPythonPath}` : envPythonPath;
5454
this.envVariables.PYTHONUNBUFFERED = '1';
5555
}
@@ -65,34 +65,34 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
6565
noop();
6666
}
6767
public async getInterpreterInformation(): Promise<InterpreterInfomation | undefined> {
68-
const msg = {args: ['GetPythonVersion']};
68+
const msg = { args: ['GetPythonVersion'] };
6969
return this.wrapCall(daemon => daemon.getInterpreterInformation(), msg);
7070
}
7171
public async getExecutablePath(): Promise<string> {
72-
const msg = {args: ['getExecutablePath']};
72+
const msg = { args: ['getExecutablePath'] };
7373
return this.wrapCall(daemon => daemon.getExecutablePath(), msg);
7474
}
7575
public getExecutionInfo(args: string[]): PythonExecutionInfo {
7676
return this.pythonExecutionService.getExecutionInfo(args);
7777
}
7878
public async isModuleInstalled(moduleName: string): Promise<boolean> {
79-
const msg = {args: ['-m', moduleName]};
79+
const msg = { args: ['-m', moduleName] };
8080
return this.wrapCall(daemon => daemon.isModuleInstalled(moduleName), msg);
8181
}
8282
public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
83-
const msg = {args, options};
83+
const msg = { args, options };
8484
return this.wrapCall(daemon => daemon.exec(args, options), msg);
8585
}
8686
public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
87-
const msg = {args: ['-m', moduleName].concat(args), options};
87+
const msg = { args: ['-m', moduleName].concat(args), options };
8888
return this.wrapCall(daemon => daemon.execModule(moduleName, args, options), msg);
8989
}
9090
public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string> {
91-
const msg = {args, options};
91+
const msg = { args, options };
9292
return this.wrapObservableCall(daemon => daemon.execObservable(args, options), msg);
9393
}
9494
public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string> {
95-
const msg = {args: ['-m', moduleName].concat(args), options};
95+
const msg = { args: ['-m', moduleName].concat(args), options };
9696
return this.wrapObservableCall(daemon => daemon.execModuleObservable(moduleName, args, options), msg);
9797
}
9898
/**
@@ -103,7 +103,7 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
103103
* @returns
104104
* @memberof PythonDaemonExecutionServicePool
105105
*/
106-
protected createConnection(proc: ChildProcess){
106+
protected createConnection(proc: ChildProcess) {
107107
return createMessageConnection(new StreamMessageReader(proc.stdout), new StreamMessageWriter(proc.stdin));
108108
}
109109
@traceDecorators.error('Failed to create daemon')
@@ -131,7 +131,7 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
131131

132132
const cls = this.options.daemonClass ?? PythonDaemonExecutionService;
133133
const instance = new cls(this.pythonExecutionService, this.pythonPath, daemonProc.proc, connection);
134-
if (instance instanceof PythonDaemonExecutionService){
134+
if (instance instanceof PythonDaemonExecutionService) {
135135
this.disposables.push(instance);
136136
return instance;
137137
}
@@ -153,7 +153,7 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
153153
* @returns {Promise<T>}
154154
* @memberof PythonDaemonExecutionServicePool
155155
*/
156-
private async wrapCall<T>(cb: (daemon: IPythonExecutionService) => Promise<T>, daemonLogMessage: {args: string[]; options?: SpawnOptions}): Promise<T> {
156+
private async wrapCall<T>(cb: (daemon: IPythonExecutionService) => Promise<T>, daemonLogMessage: { args: string[]; options?: SpawnOptions }): Promise<T> {
157157
const daemon = await this.popDaemonFromPool();
158158
try {
159159
// When using the daemon, log the message ourselves.
@@ -175,7 +175,7 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
175175
* @returns {ObservableExecutionResult<string>}
176176
* @memberof PythonDaemonExecutionServicePool
177177
*/
178-
private wrapObservableCall(cb: (daemon: IPythonExecutionService) => ObservableExecutionResult<string>, daemonLogMessage: {args: string[]; options?: SpawnOptions}): ObservableExecutionResult<string> {
178+
private wrapObservableCall(cb: (daemon: IPythonExecutionService) => ObservableExecutionResult<string>, daemonLogMessage: { args: string[]; options?: SpawnOptions }): ObservableExecutionResult<string> {
179179
const execService = this.popDaemonFromObservablePool();
180180
// Possible the daemon returned is a standard python execution service.
181181
const daemonProc = execService instanceof PythonDaemonExecutionService ? execService.proc : undefined;
@@ -187,19 +187,19 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
187187
const result = cb(execService);
188188
let completed = false;
189189
const completeHandler = () => {
190-
if (completed){
190+
if (completed) {
191191
return;
192192
}
193193
completed = true;
194-
if (!daemonProc || (!daemonProc.killed && ProcessService.isAlive(daemonProc.pid))){
194+
if (!daemonProc || (!daemonProc.killed && ProcessService.isAlive(daemonProc.pid))) {
195195
this.pushDaemonIntoPool('ObservableDaemon', execService);
196196
} else {
197197
// Possible daemon is dead (explicitly killed or died due to some error).
198198
this.addDaemonService('ObservableDaemon').ignoreErrors();
199199
}
200200
};
201201

202-
if (daemonProc){
202+
if (daemonProc) {
203203
daemonProc.on('exit', completeHandler);
204204
daemonProc.on('close', completeHandler);
205205
}
@@ -270,13 +270,13 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
270270
const testAndPushIntoPool = async () => {
271271
const daemonService = (daemon as PythonDaemonExecutionService);
272272
let procIsDead = false;
273-
if (!daemonService.isAlive || daemonService.proc.killed || !ProcessService.isAlive(daemonService.proc.pid)){
273+
if (!daemonService.isAlive || daemonService.proc.killed || !ProcessService.isAlive(daemonService.proc.pid)) {
274274
procIsDead = true;
275275
} else {
276276
// Test sending a ping.
277277
procIsDead = await this.testDaemon(daemonService.connection).then(() => false).catch(() => true);
278278
}
279-
if (procIsDead){
279+
if (procIsDead) {
280280
// The process is dead, create a new daemon.
281281
await this.addDaemonService(type);
282282
try {
@@ -301,10 +301,10 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
301301
* @memberof PythonDaemonExecutionServicePool
302302
*/
303303
@traceDecorators.error('Pinging Daemon Failed')
304-
private async testDaemon(connection: MessageConnection){
304+
private async testDaemon(connection: MessageConnection) {
305305
// If we don't get a reply to the ping in 5 seconds assume it will never work. Bomb out.
306306
// At this point there should be some information logged in stderr of the daemon process.
307-
const fail = createDeferred<{pong: string}>();
307+
const fail = createDeferred<{ pong: string }>();
308308
const timer = setTimeout(() => fail.reject(new Error('Timeout waiting for daemon to start')), 5_000);
309309
const request = new RequestType<{ data: string }, { pong: string }, void, void>('ping');
310310
// Check whether the daemon has started correctly, by sending a ping.

src/client/datascience/interactive-window/interactiveWindowCommandListener.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export class InteractiveWindowCommandListener implements IDataScienceCommandList
194194

195195
@captureTelemetry(Telemetry.ExportPythonFileAndOutput, undefined, false)
196196
private async exportFileAndOutput(file: string): Promise<Uri | undefined> {
197-
if (file && file.length > 0 && this.jupyterExecution.isNotebookSupported()) {
197+
if (file && file.length > 0 && await this.jupyterExecution.isNotebookSupported()) {
198198
// If the current file is the active editor, then generate cells from the document.
199199
const activeEditor = this.documentManager.activeTextEditor;
200200
if (activeEditor && activeEditor.document && this.fileSystem.arePathsSame(activeEditor.document.fileName, file)) {

src/client/datascience/jupyter/jupyterExecution.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33
'use strict';
44
import * as uuid from 'uuid/v4';
5-
import { CancellationToken, Event, EventEmitter } from 'vscode';
5+
import { CancellationToken, CancellationTokenSource, Event, EventEmitter } from 'vscode';
66

77
import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types';
88
import { Cancellation } from '../../common/cancellation';
@@ -111,19 +111,26 @@ export class JupyterExecutionBase implements IJupyterExecution {
111111
//tslint:disable:cyclomatic-complexity max-func-body-length
112112
public connectToNotebookServer(options?: INotebookServerOptions, cancelToken?: CancellationToken): Promise<INotebookServer | undefined> {
113113
// Return nothing if we cancel
114+
// tslint:disable-next-line: max-func-body-length
114115
return Cancellation.race(async () => {
115116
let result: INotebookServer | undefined;
116117
let connection: IConnection | undefined;
117118
let kernelSpecInterpreter: KernelSpecInterpreter | undefined;
118119
let kernelSpecInterpreterPromise: Promise<KernelSpecInterpreter> = Promise.resolve({});
119120
traceInfo(`Connecting to ${options ? options.purpose : 'unknown type of'} server`);
121+
const kernelSpecCancelSource = new CancellationTokenSource();
122+
if (cancelToken) {
123+
cancelToken.onCancellationRequested(() => {
124+
kernelSpecCancelSource.cancel();
125+
});
126+
}
120127
const isLocalConnection = !options || !options.uri;
121128

122129
if (isLocalConnection) {
123130
// Get hold of the kernelspec and corresponding (matching) interpreter that'll be used as the spec.
124131
// We can do this in parallel, while starting the server (faster).
125132
traceInfo(`Getting kernel specs for ${options ? options.purpose : 'unknown type of'} server`);
126-
kernelSpecInterpreterPromise = this.kernelSelector.getKernelForLocalConnection(undefined, options?.metadata, cancelToken);
133+
kernelSpecInterpreterPromise = this.kernelSelector.getKernelForLocalConnection(undefined, options?.metadata, kernelSpecCancelSource.token);
127134
}
128135

129136
// Try to connect to our jupyter process. Check our setting for the number of tries
@@ -176,7 +183,7 @@ export class JupyterExecutionBase implements IJupyterExecution {
176183
const sessionManagerFactory = this.serviceContainer.get<IJupyterSessionManagerFactory>(IJupyterSessionManagerFactory);
177184
const sessionManager = await sessionManagerFactory.create(connection);
178185
const kernelInterpreter = await this.kernelSelector.selectLocalKernel(sessionManager, cancelToken, launchInfo.kernelSpec);
179-
if (Object.keys(kernelInterpreter).length > 0){
186+
if (Object.keys(kernelInterpreter).length > 0) {
180187
launchInfo.interpreter = kernelInterpreter.interpreter;
181188
launchInfo.kernelSpec = kernelInterpreter.kernelSpec || kernelInterpreter.kernelModel;
182189
continue;
@@ -205,6 +212,8 @@ export class JupyterExecutionBase implements IJupyterExecution {
205212
connection?.dispose();
206213
tryCount += 1;
207214
} else if (connection) {
215+
kernelSpecCancelSource.cancel();
216+
208217
// Something else went wrong
209218
if (!isLocalConnection) {
210219
sendTelemetryEvent(Telemetry.ConnectRemoteFailedJupyter);
@@ -221,6 +230,7 @@ export class JupyterExecutionBase implements IJupyterExecution {
221230
throw new Error(localize.DataScience.jupyterNotebookConnectFailed().format(connection.baseUrl, err));
222231
}
223232
} else {
233+
kernelSpecCancelSource.cancel();
224234
throw err;
225235
}
226236
}

src/test/common/process/pythonDaemonPool.unit.test.ts

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ suite('Daemon - Python Daemon Pool', () => {
7272
}
7373
test('Create daemons when initializing', async () => {
7474
// Create and initialize the pool.
75-
const pool = new DaemonPool(logger, [], {pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
75+
const pool = new DaemonPool(logger, [], { pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
7676
await setupDaemon(pool);
7777

7878
// 2 = 2 for standard daemon + 1 observable daemon.
@@ -126,42 +126,46 @@ suite('Daemon - Python Daemon Pool', () => {
126126
const getInterpreterInformationStub = sinon.stub(PythonDaemonExecutionService.prototype, 'getInterpreterInformation');
127127
const interpreterInfoFromDaemon: InterpreterInfomation = { pythonPath: 1 } as any;
128128
const interpreterInfoFromPythonProc: InterpreterInfomation = { pythonPath: 2 } as any;
129-
// Delay returning interpreter info for 1.5 seconds.
130-
getInterpreterInformationStub.returns(sleep(1_500).then(() => interpreterInfoFromDaemon));
131-
when(pythonExecService.getInterpreterInformation()).thenResolve(interpreterInfoFromPythonProc);
132129

133-
// Create and initialize the pool.
134-
const pool = new DaemonPool(logger, [], { daemonCount: 2, observableDaemonCount: 1, pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
135-
await setupDaemon(pool);
136-
137-
// 3 = 2 for standard daemon + 1 observable daemon.
138-
expect(sendRequestStub.callCount).equal(3);
139-
expect(listenStub.callCount).equal(3);
140-
141-
const [info1, info2, info3, info4] = await Promise.all([
142-
pool.getInterpreterInformation(),
143-
pool.getInterpreterInformation(),
144-
pool.getInterpreterInformation(),
145-
pool.getInterpreterInformation()
146-
]);
147-
148-
// Verify we used the daemon.
149-
expect(getInterpreterInformationStub.callCount).to.equal(2);
150-
// Verify we used the python execution service.
151-
verify(pythonExecService.getInterpreterInformation()).twice();
152-
153-
expect(info1).to.deep.equal(interpreterInfoFromDaemon);
154-
expect(info2).to.deep.equal(interpreterInfoFromDaemon);
155-
expect(info3).to.deep.equal(interpreterInfoFromPythonProc);
156-
expect(info4).to.deep.equal(interpreterInfoFromPythonProc);
130+
try {
131+
// Delay returning interpreter info for 1.5 seconds.
132+
getInterpreterInformationStub.value(() => sleep(1_500).then(() => interpreterInfoFromDaemon));
133+
when(pythonExecService.getInterpreterInformation()).thenResolve(interpreterInfoFromPythonProc);
134+
135+
// Create and initialize the pool.
136+
const pool = new DaemonPool(logger, [], { daemonCount: 2, observableDaemonCount: 1, pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
137+
await setupDaemon(pool);
138+
139+
// 3 = 2 for standard daemon + 1 observable daemon.
140+
expect(sendRequestStub.callCount).equal(3);
141+
expect(listenStub.callCount).equal(3);
142+
143+
const [info1, info2, info3, info4] = await Promise.all([
144+
pool.getInterpreterInformation(),
145+
pool.getInterpreterInformation(),
146+
pool.getInterpreterInformation(),
147+
pool.getInterpreterInformation()
148+
]);
149+
150+
// Verify we used the python execution service.
151+
verify(pythonExecService.getInterpreterInformation()).twice();
152+
153+
expect(info1).to.deep.equal(interpreterInfoFromDaemon);
154+
expect(info2).to.deep.equal(interpreterInfoFromDaemon);
155+
expect(info3).to.deep.equal(interpreterInfoFromPythonProc);
156+
expect(info4).to.deep.equal(interpreterInfoFromPythonProc);
157+
} finally {
158+
// Make sure to remove the stub or other tests will take too long.
159+
getInterpreterInformationStub.restore();
160+
}
157161
}).timeout(3_000);
158162
test('If executing python is fast, then use the daemon (for observables)', async () => {
159163
const execModuleObservable = sinon.stub(PythonDaemonExecutionService.prototype, 'execModuleObservable');
160164
const out = new Observable<Output<string>>(s => {
161-
s.next({source: 'stdout', out: ''});
165+
s.next({ source: 'stdout', out: '' });
162166
s.complete();
163167
});
164-
execModuleObservable.returns({out} as any);
168+
execModuleObservable.returns({ out } as any);
165169

166170
// Create and initialize the pool.
167171
const pool = new DaemonPool(logger, [], { daemonCount: 1, observableDaemonCount: 1, pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
@@ -173,7 +177,7 @@ suite('Daemon - Python Daemon Pool', () => {
173177

174178
// Invoke the execModuleObservable method twice (one to use daemon, other will use python exec service).
175179
reset(pythonExecService);
176-
when(pythonExecService.execModuleObservable(anything(), anything(), anything())).thenReturn(({out} as any));
180+
when(pythonExecService.execModuleObservable(anything(), anything(), anything())).thenReturn(({ out } as any));
177181
await Promise.all([pool.execModuleObservable('x', [], {}), pool.execModuleObservable('x', [], {})]);
178182

179183
// Verify we used the daemon.

0 commit comments

Comments
 (0)