diff --git a/src/client/datascience/jupyter/jupyterUtils.ts b/src/client/datascience/jupyter/jupyterUtils.ts index dcfe07cc8367..3d0925682c6a 100644 --- a/src/client/datascience/jupyter/jupyterUtils.ts +++ b/src/client/datascience/jupyter/jupyterUtils.ts @@ -3,10 +3,12 @@ 'use strict'; import '../../common/extensions'; +import * as fs from 'fs-extra'; import * as path from 'path'; import { Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; +import { Resource } from '../../common/types'; import { noop } from '../../common/utils/misc'; import { SystemVariables } from '../../common/variables/systemVariables'; import { getJupyterConnectionDisplayName } from '../jupyter/jupyterConnection'; @@ -77,3 +79,14 @@ export function createRemoteConnectionInfo( getAuthHeader: serverUri ? () => getJupyterServerUri(uri)?.authorizationHeader : undefined }; } + +export async function computeWorkingDirectory(resource: Resource, workspace: IWorkspaceService): Promise { + // Returning directly doesn't seem to work (typescript complains) + // tslint:disable-next-line: no-unnecessary-local-variable + const workingDirectory = + resource && resource.scheme === 'file' && (await fs.pathExists(path.dirname(resource.fsPath))) + ? path.dirname(resource.fsPath) + : workspace.getWorkspaceFolder(resource)?.uri.fsPath || process.cwd(); + + return workingDirectory; +} diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts index 0bd15771a12b..e97c52c56e9b 100644 --- a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts +++ b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts @@ -6,7 +6,6 @@ import '../../../common/extensions'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); import * as os from 'os'; -import * as path from 'path'; import * as vscode from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import * as vsls from 'vsls/vscode'; @@ -40,6 +39,7 @@ import { INotebookServerLaunchInfo } from '../../types'; import { JupyterServerBase } from '../jupyterServer'; +import { computeWorkingDirectory } from '../jupyterUtils'; import { KernelSelector } from '../kernels/kernelSelector'; import { HostJupyterNotebook } from './hostJupyterNotebook'; import { LiveShareParticipantHost } from './liveShareParticipantMixin'; @@ -217,10 +217,7 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas } // Figure out the working directory we need for our new notebook. - const workingDirectory = - resource && resource.scheme === 'file' - ? path.dirname(resource.fsPath) - : this.workspaceService.getWorkspaceFolder(resource)?.uri.fsPath || process.cwd(); + const workingDirectory = await computeWorkingDirectory(resource, this.workspaceService); // Start a session (or use the existing one if allowed) const session = diff --git a/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts b/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts index a34ee3066830..362e6dfd8e16 100644 --- a/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts +++ b/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts @@ -4,6 +4,7 @@ 'use strict'; import { ChildProcess } from 'child_process'; +import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import { IDisposable } from 'monaco-editor'; import { ObservableExecutionResult } from '../../common/process/types'; @@ -29,7 +30,10 @@ export class PythonKernelLauncherDaemon implements IDisposable { kernelSpec: IJupyterKernelSpec, interpreter?: PythonInterpreter ): Promise<{ observableOutput: ObservableExecutionResult; daemon: IPythonKernelDaemon | undefined }> { - const daemon = await this.daemonPool.get(resource, kernelSpec, interpreter); + const [daemon, wdExists] = await Promise.all([ + this.daemonPool.get(resource, kernelSpec, interpreter), + fs.pathExists(workingDirectory) + ]); // Check to see if we have the type of kernelspec that we expect const args = kernelSpec.argv.slice(); @@ -51,7 +55,7 @@ export class PythonKernelLauncherDaemon implements IDisposable { // If we don't have a KernelDaemon here then we have an execution service and should use that to launch const observableOutput = daemon.execModuleObservable(moduleName, moduleArgs, { env, - cwd: workingDirectory + cwd: wdExists ? workingDirectory : process.cwd() }); return { observableOutput, daemon: undefined }; diff --git a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts b/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts index 8abdf4592a39..3828f74244bd 100644 --- a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts +++ b/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts @@ -3,7 +3,6 @@ 'use strict'; import '../../../common/extensions'; -import * as path from 'path'; import * as vscode from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import * as vsls from 'vsls/vscode'; @@ -24,6 +23,7 @@ import * as localize from '../../../common/utils/localize'; import { noop } from '../../../common/utils/misc'; import { IServiceContainer } from '../../../ioc/types'; import { Identifiers, LiveShare, LiveShareCommands, Settings } from '../../constants'; +import { computeWorkingDirectory } from '../../jupyter/jupyterUtils'; import { KernelSelector, KernelSpecInterpreter } from '../../jupyter/kernels/kernelSelector'; import { HostJupyterNotebook } from '../../jupyter/liveshare/hostJupyterNotebook'; import { LiveShareParticipantHost } from '../../jupyter/liveshare/liveShareParticipantMixin'; @@ -142,10 +142,7 @@ export class HostRawNotebookProvider ? this.progressReporter.createProgressIndicator(localize.DataScience.connectingIPyKernel()) : undefined; - const workingDirectory = - resource && resource.scheme === 'file' - ? path.dirname(resource.fsPath) - : this.workspaceService.getWorkspaceFolder(resource)?.uri.fsPath || process.cwd(); + const workingDirectory = await computeWorkingDirectory(resource, this.workspaceService); const rawSession = new RawJupyterSession( this.kernelLauncher, diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 46dfe6004a1c..504e3499f401 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -577,35 +577,41 @@ for i in range(0, 100): runTest( 'Interrupt double', - async () => { - let interruptedKernel = false; - const { window, mount } = await getOrCreateInteractiveWindow(ioc); - window.notebook?.onKernelInterrupted(() => (interruptedKernel = true)); - - let timerCount = 0; - addContinuousMockData(ioc, interruptCode, async (_c) => { - timerCount += 1; - await sleep(0.5); - return Promise.resolve({ result: '', haveMore: timerCount < 100 }); - }); + async (context: Mocha.Context) => { + if (ioc.mockJupyter) { + let interruptedKernel = false; + const { window, mount } = await getOrCreateInteractiveWindow(ioc); + window.notebook?.onKernelInterrupted(() => (interruptedKernel = true)); + + let timerCount = 0; + addContinuousMockData(ioc, interruptCode, async (_c) => { + timerCount += 1; + await sleep(0.5); + return Promise.resolve({ result: '', haveMore: timerCount < 100 }); + }); - addMockData(ioc, interruptCode, undefined, 'text/plain'); + addMockData(ioc, interruptCode, undefined, 'text/plain'); - // Run the interrupt code and then interrupt it twice to make sure we can interrupt twice - const waitForAdd = addCode(ioc, interruptCode); + // Run the interrupt code and then interrupt it twice to make sure we can interrupt twice + const waitForAdd = addCode(ioc, interruptCode); - // 'Click' the button in the react control. We need to verify we can - // click it more than once. - const interrupt = findButton(mount.wrapper, InteractivePanel, 4); - interrupt?.simulate('click'); - await sleep(0.1); - interrupt?.simulate('click'); + // 'Click' the button in the react control. We need to verify we can + // click it more than once. + const interrupt = findButton(mount.wrapper, InteractivePanel, 4); + interrupt?.simulate('click'); + await sleep(0.1); + interrupt?.simulate('click'); - // We should get out of the wait for add - await waitForAdd; + // We should get out of the wait for add + await waitForAdd; - // We should have also fired an interrupt - assert.ok(interruptedKernel, 'Kernel was not interrupted'); + // We should have also fired an interrupt + assert.ok(interruptedKernel, 'Kernel was not interrupted'); + } else { + // Timing is too iffy for real jupyter. However we really just + // want to make sure double interrupt is supported so keep the test. + context.skip(); + } }, () => { return ioc; diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index d8612e5a957b..86b971f1d0b2 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -317,6 +317,9 @@ suite('DataScience Native Editor', () => { }); runMountedTest('Remote kernel can be switched and remembered', async () => { + // Turn off raw kernel for this test as it's testing remote + ioc.setExperimentState(LocalZMQKernel.experiment, false); + const pythonService = await createPythonService(ioc, 2); // Skip test for older python and raw kernel and mac @@ -341,9 +344,16 @@ suite('DataScience Native Editor', () => { // Create another notebook and connect it to the already running kernel of the other one when(ioc.applicationShell.showQuickPick(anything(), anything(), anything())).thenCall( async (o: IKernelSpecQuickPickItem[]) => { - const existing = o.find((s) => s.selection.kernelModel?.numberOfConnections); - if (existing) { - return existing; + const existing = o.filter((s) => s.selection.kernelModel?.numberOfConnections); + + // Might be more than one. Get the oldest one. It has the actual activity. + const sorted = existing.sort( + (a, b) => + b.selection.kernelModel!.lastActivityTime.getTime() - + a.selection.kernelModel!.lastActivityTime.getTime() + ); + if (sorted && sorted.length) { + return sorted[0]; } } ); diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index c16783a99543..4eb84eaf390a 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -403,7 +403,7 @@ suite('DataScience notebook tests', () => { async () => { const pythonService = await createPythonService(ioc); - if (pythonService) { + if (pythonService && !useRawKernel) { const configFile = path.join( EXTENSION_ROOT_DIR, 'src', @@ -443,7 +443,7 @@ suite('DataScience notebook tests', () => { const pythonService = await createPythonService(ioc); - if (pythonService) { + if (pythonService && !useRawKernel) { const configFile = path.join( EXTENSION_ROOT_DIR, 'src', @@ -509,7 +509,7 @@ suite('DataScience notebook tests', () => { runTest('Remote', async () => { const pythonService = await createPythonService(ioc); - if (pythonService) { + if (pythonService && !useRawKernel) { const configFile = path.join( EXTENSION_ROOT_DIR, 'src', diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index 7747db5b3fdf..287e4a0ee4ed 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -14,6 +14,7 @@ import * as path from 'path'; import * as sinon from 'sinon'; import { Disposable } from 'vscode'; import { LocalZMQKernel } from '../../../client/common/experiments/groups'; +import { sleep } from '../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../client/constants'; import { retryIfFail as retryIfFailOriginal } from '../../common'; import { mockedVSCodeNamespaces } from '../../vscode-mock'; @@ -170,6 +171,9 @@ use(chaiAsPromised); async function verifySliderWidgetIsAvailableAfterExecution(notebookUI: NotebookEditorUI) { await notebookUI.executeCell(0); + // Slider output could take a bit. Wait some + await sleep(2000); + await retryIfFail(async () => { await assert.eventually.isTrue(notebookUI.cellHasOutput(0)); const outputHtml = await notebookUI.getCellOutputHTML(0);