Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Ensure working directory is an actual directory #13403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/client/datascience/jupyter/jupyterUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,3 +79,14 @@ export function createRemoteConnectionInfo(
getAuthHeader: serverUri ? () => getJupyterServerUri(uri)?.authorizationHeader : undefined
};
}

export async function computeWorkingDirectory(resource: Resource, workspace: IWorkspaceService): Promise<string> {
// 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;
}
7 changes: 2 additions & 5 deletions src/client/datascience/jupyter/liveshare/hostJupyterServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,7 +30,10 @@ export class PythonKernelLauncherDaemon implements IDisposable {
kernelSpec: IJupyterKernelSpec,
interpreter?: PythonInterpreter
): Promise<{ observableOutput: ObservableExecutionResult<string>; 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();
Expand All @@ -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()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really necessary but just wanted to double check.

});

return { observableOutput, daemon: undefined };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
54 changes: 30 additions & 24 deletions src/test/datascience/interactiveWindow.functional.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 13 additions & 3 deletions src/test/datascience/nativeEditor.functional.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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];
}
}
);
Expand Down
6 changes: 3 additions & 3 deletions src/test/datascience/notebook.functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions src/test/datascience/uiTests/ipywidget.ui.functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down