From 8734a9b37cba6b4912c6bfdb943ebfe25505d9d7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 19 Nov 2021 11:31:28 -0800 Subject: [PATCH] Recommend updating traitlets to 5.1.1 if jupyter fails to start (#8307) --- news/2 Fixes/8295.md | 1 + package.nls.json | 3 + src/client/common/errors/errorUtils.ts | 47 ++++++- src/client/common/utils/localize.ts | 12 ++ src/client/datascience/errors/errorHandler.ts | 102 ++++++++++++-- .../datascience/jupyter/jupyterConnection.ts | 8 +- .../datascience/jupyter/kernels/kernel.ts | 4 + .../datascience/jupyter/notebookStarter.ts | 3 +- src/client/logging/trace.ts | 3 + src/datascience-ui/error-renderer/index.ts | 6 + .../datascience/errorHandler.unit.test.ts | 124 ++++++++++++++++-- 11 files changed, 285 insertions(+), 28 deletions(-) create mode 100644 news/2 Fixes/8295.md diff --git a/news/2 Fixes/8295.md b/news/2 Fixes/8295.md new file mode 100644 index 00000000000..d6182445245 --- /dev/null +++ b/news/2 Fixes/8295.md @@ -0,0 +1 @@ +Recommend installing `traitlets` version `5.1.1` if Jupyter fails to start and the version of `traitlets` is older. diff --git a/package.nls.json b/package.nls.json index ac05bff7361..495cf97f812 100644 --- a/package.nls.json +++ b/package.nls.json @@ -453,6 +453,9 @@ "DataScience.failedToStartKernelDueToPyZmqFailure": "The kernel failed to start due to an error with the 'pyzmq' module. Consider re-installing this module.", "DataScience.failedToStartKernelDueToOldIPython": "The kernel failed to start due to an outdated version of IPython. Consider updating this module to the latest version.", "DataScience.failedToStartKernelDueToOldIPyKernel": "The kernel failed to start due to an outdated version of IPyKernel. Consider updating this module to the latest version.", + "DataScience.failedToStartJupyter": "Failed to start Jupyter in the environment '{0}'. \nView Jupyter [log](command:jupyter.viewOutput) for further details.", + "DataScience.failedToStartJupyterWithErrorInfo": "Failed to start Jupyter in the environment '{0}'. \n{1} \nView Jupyter [log](command:jupyter.viewOutput) for further details.", + "DataScience.failedToStartJupyterDueToOutdatedTraitlets": "Failed to start Jupyter in the environment '{0}' possibly due to an outdated version of 'traitlets'. \n{1} \nConsider updating the 'traitlets' module to '5.1.1' or later. \nView Jupyter [log](command:jupyter.viewOutput) for further details.", "DataScience.cannotRunCellKernelIsDead": "Cannot run cells, as the kernel '{0}' is dead.", "DataScience.waitingForJupyterSessionToBeIdle": "Waiting for Jupyter Session to be idle", "DataScience.gettingListOfKernelsForLocalConnection": "Fetching Kernels", diff --git a/src/client/common/errors/errorUtils.ts b/src/client/common/errors/errorUtils.ts index 2396eb908d2..fc978731a1c 100644 --- a/src/client/common/errors/errorUtils.ts +++ b/src/client/common/errors/errorUtils.ts @@ -157,7 +157,15 @@ export enum KernelFailureReason { * import win32api * ImportError: DLL load failed: The specified procedure could not be found. */ - dllLoadFailure = 'dllLoadFailure' + dllLoadFailure = 'dllLoadFailure', + /** + * Failure to start Jupyter due to some unknown reason. + */ + jupyterStartFailure = 'jupyterStartFailure', + /** + * Failure to start Jupyter due to outdated traitlets. + */ + jupyterStartFailureOutdatedTraitlets = 'jupyterStartFailureOutdatedTraitlets' } type BaseFailure = { reason: Reason; @@ -208,6 +216,15 @@ export type DllLoadFailure = BaseFailure< moduleName?: string; } >; +export type JupyterStartFailure = BaseFailure< + KernelFailureReason.jupyterStartFailure | KernelFailureReason.jupyterStartFailureOutdatedTraitlets, + { + /** + * Python error message displayed. + */ + errorMessage?: string; + } +>; export type ImportWin32ApiFailure = BaseFailure; export type ZmqModuleFailure = BaseFailure; export type OldIPyKernelFailure = BaseFailure; @@ -222,12 +239,14 @@ export type KernelFailure = | ImportWin32ApiFailure | ZmqModuleFailure | OldIPyKernelFailure + | JupyterStartFailure | OldIPythonFailure; export function analyzeKernelErrors( stdErrOrStackTrace: string, workspaceFolders: readonly WorkspaceFolder[] = [], - pythonSysPrefix: string = '' + pythonSysPrefix: string = '', + isJupyterStartupError?: boolean ): KernelFailure | undefined { const lastTwolinesOfError = getLastTwoLinesFromPythonTracebackWithErrorMessage(stdErrOrStackTrace); const stdErr = stdErrOrStackTrace.toLowerCase(); @@ -398,6 +417,30 @@ export function analyzeKernelErrors( }; } } + if (isJupyterStartupError) { + // Get the last error message in the stack trace. + let errorMessage = stdErrOrStackTrace + .splitLines() + .map((line) => line.trim()) + .reverse() + .find((line) => line.toLowerCase().includes('Error: '.toLowerCase())); + // https://github.com/microsoft/vscode-jupyter/issues/8295 + const errorMessageDueToOutdatedTraitlets = "AttributeError: 'Namespace' object has no attribute '_flags'"; + const telemetrySafeTags = ['jupyter.startup.failure']; + let reason = KernelFailureReason.jupyterStartFailure; + if (stdErr.includes(errorMessageDueToOutdatedTraitlets.toLowerCase())) { + reason = KernelFailureReason.jupyterStartFailureOutdatedTraitlets; + errorMessage = errorMessageDueToOutdatedTraitlets; + telemetrySafeTags.push('outdated.traitlets'); + } + if (errorMessage) { + return { + reason, + errorMessage, + telemetrySafeTags + }; + } + } } function extractModuleAndFileFromImportError(errorLine: string) { diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index f9e722add0a..6d7eb82b973 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -379,6 +379,18 @@ export namespace DataScience { 'DataScience.sessionStartFailedWithKernel', "Failed to start the Kernel '{0}'. \nView Jupyter [log](command:jupyter.viewOutput) for further details." ); + export const failedToStartJupyter = localize( + 'DataScience.failedToStartJupyter', + "Failed to start Jupyter in the environment '{0}'. \nView Jupyter [log](command:jupyter.viewOutput) for further details." + ); + export const failedToStartJupyterWithErrorInfo = localize( + 'DataScience.failedToStartJupyterWithErrorInfo', + "Failed to start Jupyter in the environment '{0}'. \n{1} \nView Jupyter [log](command:jupyter.viewOutput) for further details." + ); + export const failedToStartJupyterDueToOutdatedTraitlets = localize( + 'DataScience.failedToStartJupyterDueToOutdatedTraitlets', + "Failed to start Jupyter in the environment '{0}' possibly due to an outdated version of 'traitlets'. \n{1} \nConsider updating the 'traitlets' module to '5.1.1' or later. \nView Jupyter [log](command:jupyter.viewOutput) for further details." + ); export const failedToStartKernel = localize('DataScience.failedToStartKernel', 'Failed to start the Kernel.'); export const failedToRestartKernel = localize('DataScience.failedToRestartKernel', 'Failed to restart the Kernel.'); export const failedToInterruptKernel = localize( diff --git a/src/client/datascience/errors/errorHandler.ts b/src/client/datascience/errors/errorHandler.ts index cbdb2f28edd..be81eca5736 100644 --- a/src/client/datascience/errors/errorHandler.ts +++ b/src/client/datascience/errors/errorHandler.ts @@ -35,18 +35,21 @@ import { PythonKernelDiedError } from './pythonKernelDiedError'; import { analyzeKernelErrors, getErrorMessageFromPythonTraceback, + KernelFailure, KernelFailureReason } from '../../common/errors/errorUtils'; import { IKernelProvider, KernelConnectionMetadata } from '../jupyter/kernels/types'; import { getDisplayPath } from '../../common/platform/fs-paths'; import { IBrowserService, IConfigurationService, Resource } from '../../common/types'; -import { Telemetry } from '../constants'; +import { Commands, Telemetry } from '../constants'; import { sendTelemetryEvent } from '../../telemetry'; import { DisplayOptions } from '../displayOptions'; import { IServiceContainer } from '../../ioc/types'; import { StopWatch } from '../../common/utils/stopWatch'; import { sleep } from '../../common/utils/async'; import { INotebookControllerManager } from '../notebook/types'; +import { JupyterConnectError } from './jupyterConnectError'; +import { JupyterInterpreterService } from '../jupyter/interpreter/jupyterInterpreterService'; @injectable() export class DataScienceErrorHandler implements IDataScienceErrorHandler { @@ -58,6 +61,7 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { @inject(IBrowserService) private readonly browser: IBrowserService, @inject(IConfigurationService) private readonly configuration: IConfigurationService, @inject(IKernelDependencyService) private readonly kernelDependency: IKernelDependencyService, + @inject(JupyterInterpreterService) private readonly jupyterInterpreter: JupyterInterpreterService, @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer ) {} public async handleError(err: Error): Promise { @@ -77,6 +81,12 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { purpose, cellToDisplayErrors, async (error: BaseError, defaultErrorMessage?: string) => { + const failureInfo = analyzeKernelErrors( + error.stdErr || '', + this.workspace.workspaceFolders, + kernelConnection.interpreter?.sysPrefix, + err instanceof JupyterConnectError + ); if ( err instanceof IpyKernelNotInstalledError && err.reason === KernelInterpreterDependencyResponse.uiHidden && @@ -96,13 +106,11 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { ) .finally(() => token.dispose()); return; + } else if (err instanceof JupyterConnectError) { + void this.handleJupyterStartupError(failureInfo, err, kernelConnection, cellToDisplayErrors); + return; } - const failureInfo = analyzeKernelErrors( - error.stdErr || '', - this.workspace.workspaceFolders, - kernelConnection.interpreter?.sysPrefix - ); switch (failureInfo?.reason) { case KernelFailureReason.overridingBuiltinModules: { await this.showMessageWithMoreInfo( @@ -219,15 +227,78 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { } ); } - private async showMessageWithMoreInfo(message: string, moreInfoLink: string, cellToDisplayErrors?: NotebookCell) { - message = `${message} \n${DataScience.viewJupyterLogForFurtherInfo()}`; - void this.displayErrorsInCell(message, cellToDisplayErrors); - await this.applicationShell.showErrorMessage(message, Common.learnMore()).then((selection) => { - if (selection === Common.learnMore()) { + private async showMessageWithMoreInfo( + message: string, + moreInfoLink: string | undefined, + cellToDisplayErrors?: NotebookCell + ) { + if (!message.includes(Commands.ViewJupyterOutput)) { + message = `${message} \n${DataScience.viewJupyterLogForFurtherInfo()}`; + } + void this.displayErrorsInCell(message, cellToDisplayErrors, moreInfoLink); + const buttons = moreInfoLink ? [Common.learnMore()] : []; + await this.applicationShell.showErrorMessage(message, ...buttons).then((selection) => { + if (selection === Common.learnMore() && moreInfoLink) { this.browser.launch(moreInfoLink); } }); } + private async handleJupyterStartupError( + failureInfo: KernelFailure | undefined, + error: JupyterConnectError, + kernelConnection: KernelConnectionMetadata, + cellToDisplayErrors?: NotebookCell + ) { + const failureInfoFromMessage = + failureInfo || + analyzeKernelErrors( + error.message, + this.workspace.workspaceFolders, + kernelConnection.interpreter?.sysPrefix, + true + ); + // Extract the python error message so we can display that. + let pythonError: string | undefined = + failureInfoFromMessage?.reason === KernelFailureReason.jupyterStartFailure || + failureInfoFromMessage?.reason === KernelFailureReason.jupyterStartFailureOutdatedTraitlets + ? failureInfoFromMessage?.errorMessage + : undefined; + if (!pythonError) { + // Some times the error message is either in the message or the stderr. + pythonError = error.message + .splitLines({ removeEmptyEntries: true, trim: true }) + .reverse() + .find((item) => item.toLowerCase().includes('Error: ')); + pythonError = + pythonError || + (error.stdErr || '') + .splitLines({ removeEmptyEntries: true, trim: true }) + .reverse() + .find((item) => item.toLowerCase().includes('Error: ')); + } + const jupyterInterpreter = await this.jupyterInterpreter.getSelectedInterpreter(); + const envDisplayName = jupyterInterpreter + ? `${jupyterInterpreter.displayName} (${getDisplayPath( + jupyterInterpreter.path, + this.workspace.workspaceFolders || [] + )})` + : ''; + if ( + jupyterInterpreter && + failureInfoFromMessage?.reason === KernelFailureReason.jupyterStartFailureOutdatedTraitlets + ) { + void this.showMessageWithMoreInfo( + DataScience.failedToStartJupyterDueToOutdatedTraitlets().format(envDisplayName, pythonError || ''), + 'https://aka.ms/kernelFailuresJupyterTrailtletsOutdated', + cellToDisplayErrors + ); + } else { + const message = pythonError + ? DataScience.failedToStartJupyterWithErrorInfo().format(envDisplayName, pythonError) + : DataScience.failedToStartJupyter().format(envDisplayName); + void this.showMessageWithMoreInfo(message, undefined, cellToDisplayErrors); + } + } private async handleErrorImplementation( err: Error, purpose?: 'start' | 'restart' | 'interrupt' | 'execution', @@ -279,7 +350,8 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { } else if ( err instanceof KernelDiedError || err instanceof KernelProcessExitedError || - err instanceof PythonKernelDiedError + err instanceof PythonKernelDiedError || + err instanceof JupyterConnectError ) { const defaultErrorMessage = getCombinedErrorMessage( errorPrefix, @@ -300,7 +372,7 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { } traceError('DataScience Error', err); } - private async displayErrorsInCell(errorMessage: string, cellToDisplayErrors?: NotebookCell) { + private async displayErrorsInCell(errorMessage: string, cellToDisplayErrors?: NotebookCell, moreInfoLink?: string) { if (!cellToDisplayErrors || !errorMessage) { return; } @@ -325,6 +397,7 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { if (!controller || controller.connection !== associatedkernel.kernelConnectionMetadata) { return; } + // If we have markdown links to run a command, turn that into a link. const regex = /\[(?.*)\]\((?command:\S*)\)/gm; let matches: RegExpExecArray | undefined | null; while ((matches = regex.exec(errorMessage)) !== null) { @@ -332,6 +405,9 @@ export class DataScienceErrorHandler implements IDataScienceErrorHandler { errorMessage = errorMessage.replace(matches[0], `${matches[1]}`); } } + if (moreInfoLink) { + errorMessage += `\n${Common.learnMore()}`; + } const execution = controller.controller.createNotebookCellExecution(cellToDisplayErrors); execution.start(); void execution.clearOutput(cellToDisplayErrors); diff --git a/src/client/datascience/jupyter/jupyterConnection.ts b/src/client/datascience/jupyter/jupyterConnection.ts index 127de987cc9..e93bbd4a19a 100644 --- a/src/client/datascience/jupyter/jupyterConnection.ts +++ b/src/client/datascience/jupyter/jupyterConnection.ts @@ -7,7 +7,7 @@ import { ChildProcess } from 'child_process'; import { Subscription } from 'rxjs'; import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; import { Cancellation, CancellationError } from '../../common/cancellation'; -import { traceInfo, traceWarning } from '../../common/logger'; +import { traceError, traceInfo, traceWarning } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; import { ObservableExecutionResult, Output } from '../../common/process/types'; @@ -89,7 +89,7 @@ export class JupyterConnectionWaiter implements IDisposable { this.output(output.out); } }, - (e) => this.rejectStartPromise(e.message), + (e) => this.rejectStartPromise(e), // If the process dies, we can't extract connection information. () => this.rejectStartPromise(localize.DataScience.jupyterServerCrashed().format(exitCode)) ) @@ -155,6 +155,7 @@ export class JupyterConnectionWaiter implements IDisposable { try { url = new URL(uriString); } catch (err) { + traceError(`Failed to parse ${uriString}`, err); // Failed to parse the url either via server infos or the string this.rejectStartPromise(localize.DataScience.jupyterLaunchNoURL()); return; @@ -208,10 +209,11 @@ export class JupyterConnectionWaiter implements IDisposable { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any - private rejectStartPromise = (message: string) => { + private rejectStartPromise = (message: string | Error) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any clearTimeout(this.launchTimeout as any); if (!this.startPromise.resolved) { + message = typeof message === 'string' ? message : message.message; this.startPromise.reject( Cancellation.isCanceled(this.cancelToken) ? new CancellationError() diff --git a/src/client/datascience/jupyter/kernels/kernel.ts b/src/client/datascience/jupyter/kernels/kernel.ts index 7c75e99e13d..dc9b27e8fce 100644 --- a/src/client/datascience/jupyter/kernels/kernel.ts +++ b/src/client/datascience/jupyter/kernels/kernel.ts @@ -68,6 +68,7 @@ import { Deferred } from '../../../common/utils/async'; import { getDisplayPath } from '../../../common/platform/fs-paths'; import { WrappedError } from '../../../common/errors/types'; import { DisplayOptions } from '../../displayOptions'; +import { JupyterConnectError } from '../../errors/jupyterConnectError'; export class Kernel implements IKernel { get connection(): INotebookProviderConnection | undefined { @@ -381,6 +382,9 @@ export class Kernel implements IKernel { `failed to create INotebook in kernel, UI Disabled = ${this.startupUI.disableUI}`, ex ); + if (ex instanceof JupyterConnectError) { + throw ex; + } // Provide a user friendly message in case `ex` is some error thats not throw by us. const message = DataScience.sessionStartFailedWithKernel().format( getDisplayNameOrNameOfKernelConnection(this.kernelConnectionMetadata) diff --git a/src/client/datascience/jupyter/notebookStarter.ts b/src/client/datascience/jupyter/notebookStarter.ts index e3f542cb2c2..bda4f72947d 100644 --- a/src/client/datascience/jupyter/notebookStarter.ts +++ b/src/client/datascience/jupyter/notebookStarter.ts @@ -26,6 +26,7 @@ import { IJupyterConnection, IJupyterSubCommandExecutionService } from '../types import { JupyterConnectionWaiter } from './jupyterConnection'; import { JupyterInstallError } from '../errors/jupyterInstallError'; import { disposeAllDisposables } from '../../common/helpers'; +import { JupyterConnectError } from '../errors/jupyterConnectError'; /** * Responsible for starting a notebook. @@ -164,7 +165,7 @@ export class NotebookStarter implements Disposable { return connection; } catch (err) { disposeAllDisposables(disposables); - if (err instanceof CancellationError) { + if (err instanceof CancellationError || err instanceof JupyterConnectError) { throw err; } diff --git a/src/client/logging/trace.ts b/src/client/logging/trace.ts index 4dfb220c708..a8f3aa29764 100644 --- a/src/client/logging/trace.ts +++ b/src/client/logging/trace.ts @@ -136,6 +136,9 @@ function formatArgument(target: Object, method: MethodName, arg: any, parameterI // Where possible strip user names from paths, then users will be more likely to provide the logs. return removeUserPaths(arg.fsPath); } + if (!arg) { + return arg; + } const parameterInfos = formattedParameters.get(target)?.get(method); const info = parameterInfos?.find((info) => info.parameterIndex === parameterIndex); if (!info) { diff --git a/src/datascience-ui/error-renderer/index.ts b/src/datascience-ui/error-renderer/index.ts index 6ebccc8c7fa..b03b00b57db 100644 --- a/src/datascience-ui/error-renderer/index.ts +++ b/src/datascience-ui/error-renderer/index.ts @@ -81,6 +81,7 @@ export const activate: ActivationFunction = (_context) => { // We need to unescape them. const fileLinkRegExp = new RegExp(/<a href='file:(.*(?=\?))\?line=(\d*)'>(\d*)<\/a>/); const commandRegEx = new RegExp(/<a href='command:(.*)'>(.*)<\/a>/); + const akaMsLinks = new RegExp(/<a href='https:\/\/aka.ms\/(.*)'>(.*)<\/a>/); traceback = traceback.map((line) => { let matches: RegExpExecArray | undefined | null; while ((matches = fileLinkRegExp.exec(line)) !== null) { @@ -96,6 +97,11 @@ export const activate: ActivationFunction = (_context) => { line = line.replace(matches[0], `${matches[2]}`); } } + while ((matches = akaMsLinks.exec(line)) !== null) { + if (matches.length === 3) { + line = line.replace(matches[0], `${matches[2]}`); + } + } return line; }); diff --git a/src/test/datascience/errorHandler.unit.test.ts b/src/test/datascience/errorHandler.unit.test.ts index 6256bb0782e..a08d8045f8a 100644 --- a/src/test/datascience/errorHandler.unit.test.ts +++ b/src/test/datascience/errorHandler.unit.test.ts @@ -17,6 +17,9 @@ import { KernelConnectionMetadata } from '../../client/datascience/jupyter/kerne import { IJupyterInterpreterDependencyManager, IKernelDependencyService } from '../../client/datascience/types'; import { getOSType, OSType } from '../common'; import { IServiceContainer } from '../../client/ioc/types'; +import { JupyterInterpreterService } from '../../client/datascience/jupyter/interpreter/jupyterInterpreterService'; +import { JupyterConnectError } from '../../client/datascience/errors/jupyterConnectError'; +import { PythonEnvironment } from '../../client/pythonEnvironments/info'; suite('DataScience Error Handler Unit Tests', () => { let applicationShell: IApplicationShell; @@ -27,6 +30,13 @@ suite('DataScience Error Handler Unit Tests', () => { let configuration: IConfigurationService; let kernelDependencyInstaller: IKernelDependencyService; let svcContainer: IServiceContainer; + let jupyterInterpreterService: JupyterInterpreterService; + const jupyterInterpreter: PythonEnvironment = { + displayName: 'Hello', + path: 'Some Path', + sysPrefix: '' + }; + setup(() => { applicationShell = mock(); worksapceService = mock(); @@ -34,6 +44,7 @@ suite('DataScience Error Handler Unit Tests', () => { configuration = mock(); browser = mock(); svcContainer = mock(); + jupyterInterpreterService = mock(); kernelDependencyInstaller = mock(); when(dependencyManager.installMissingDependencies(anything())).thenResolve(); when(worksapceService.workspaceFolders).thenReturn([]); @@ -44,8 +55,11 @@ suite('DataScience Error Handler Unit Tests', () => { instance(browser), instance(configuration), instance(kernelDependencyInstaller), + instance(jupyterInterpreterService), instance(svcContainer) ); + when(applicationShell.showErrorMessage(anything())).thenResolve(); + when(applicationShell.showErrorMessage(anything(), anything())).thenResolve(); }); const message = 'Test error message.'; @@ -174,7 +188,27 @@ suite('DataScience Error Handler Unit Tests', () => { File "C:\\Python39\\lib\\tempfile.py", line 45, in from random import Random as _Random ImportError: cannot import name 'Random' from 'random' (c:\\Development\\samples\\pySamples\\sample1\\kernel_issues\\start\\random.py) - ` + `, + failureToStartJupyter: `namespace, args = self._parse_known_args(args, namespace) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 2062, in _parse_known_args + start_index = consume_optional(start_index) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 2002, in consume_optional + take_action(action, args, option_string) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 1930, in take_action + action(self, namespace, argument_values, option_string) + File "/home/don/samples/pySamples/crap/.venvJupyter/lib/python3.9/site-packages/traitlets/config/loader.py", line 913, in __call__ + raise NotImplementedError("subclasses must implement __call__") + NotImplementedError: subclasses must implement __call__`, + failureToStartJupyterDueToOutdatedTraitlets: `namespace, args = self._parse_known_args(args, namespace) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 2062, in _parse_known_args + start_index = consume_optional(start_index) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 2002, in consume_optional + take_action(action, args, option_string) + File "/home/don/miniconda3/envs/tf/lib/python3.9/argparse.py", line 1930, in take_action + action(self, namespace, argument_values, option_string) + File "/home/don/samples/pySamples/crap/.venvJupyter/lib/python3.9/site-packages/traitlets/config/loader.py", line 913, in __call__ + raise NotImplementedError("subclasses must implement __call__") + AttributeError: 'Namespace' object has no attribute '_flags'` }; test('Unable to import from user overriding module (windows)', async () => { await dataScienceErrorHandler.handleKernelError( @@ -334,14 +368,86 @@ ImportError: No module named 'xyz' verifyErrorMessage(expectedMessage, 'https://aka.ms/kernelFailuresDllLoad'); }); - function verifyErrorMessage(message: string, linkInfo: string) { - verify( - applicationShell.showErrorMessage( - `${message} \n${DataScience.viewJupyterLogForFurtherInfo()}`, - Common.learnMore() - ) - ).once(); - verify(browser.launch(linkInfo)).once(); + async function verifyJupyterErrors(stdError: string, expectedMessage: string, expectedLink?: string) { + when(jupyterInterpreterService.getSelectedInterpreter()).thenResolve(jupyterInterpreter); + when(jupyterInterpreterService.getSelectedInterpreter(anything())).thenResolve(jupyterInterpreter); + await dataScienceErrorHandler.handleKernelError( + new JupyterConnectError(stdError, `xyz`), + 'start', + kernelConnection, + undefined + ); + + verifyErrorMessage(expectedMessage, expectedLink); + } + test('Failure to start Jupyter Server (unable to extract python error message)', async () => { + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const expectedMessage = DataScience.failedToStartJupyter().format(envDisplayName); + await verifyJupyterErrors('Kaboom', expectedMessage); + }); + test('Failure to start Jupyter Server (unable to extract python error message), (without failure about jupyter error, without daemon)', async () => { + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const expectedMessage = DataScience.failedToStartJupyter().format(envDisplayName); + await verifyJupyterErrors('kaboom', expectedMessage); + }); + test('Failure to start Jupyter Server', async () => { + const stdError = `${stdErrorMessages.failureToStartJupyter} + +Failed to run jupyter as observable with args notebook --no-browser --notebook-dir="/home/don/samples/pySamples/crap" --config=/tmp/40aa74ae-d668-4225-8201-4570c9a0ac4a/jupyter_notebook_config.py --NotebookApp.iopub_data_rate_limit=10000000000.0`; + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const pythonError = 'NotImplementedError: subclasses must implement __call__'; + const expectedMessage = DataScience.failedToStartJupyterWithErrorInfo().format(envDisplayName, pythonError); + await verifyJupyterErrors(stdError, expectedMessage); + }); + test('Failure to start Jupyter Server (without failure about jupyter error, without daemon)', async () => { + const stdError = stdErrorMessages.failureToStartJupyter; + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const pythonError = 'NotImplementedError: subclasses must implement __call__'; + const expectedMessage = DataScience.failedToStartJupyterWithErrorInfo().format(envDisplayName, pythonError); + await verifyJupyterErrors(stdError, expectedMessage); + }); + test('Failure to start Jupyter Server due to outdated traitlets', async () => { + const stdError = `${stdErrorMessages.failureToStartJupyterDueToOutdatedTraitlets} + +Failed to run jupyter as observable with args notebook --no-browser --notebook-dir="/home/don/samples/pySamples/crap" --config=/tmp/40aa74ae-d668-4225-8201-4570c9a0ac4a/jupyter_notebook_config.py --NotebookApp.iopub_data_rate_limit=10000000000.0`; + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const pythonError = "AttributeError: 'Namespace' object has no attribute '_flags'"; + const expectedMessage = DataScience.failedToStartJupyterDueToOutdatedTraitlets().format( + envDisplayName, + pythonError + ); + + await verifyJupyterErrors( + stdError, + expectedMessage, + 'https://aka.ms/kernelFailuresJupyterTrailtletsOutdated' + ); + }); + test('Failure to start Jupyter Server due to outdated traitlets (without failure about jupyter error, without daemon)', async () => { + const stdError = stdErrorMessages.failureToStartJupyterDueToOutdatedTraitlets; + const envDisplayName = `${jupyterInterpreter.displayName} (${jupyterInterpreter.path})`; + const pythonError = "AttributeError: 'Namespace' object has no attribute '_flags'"; + const expectedMessage = DataScience.failedToStartJupyterDueToOutdatedTraitlets().format( + envDisplayName, + pythonError + ); + await verifyJupyterErrors( + stdError, + expectedMessage, + 'https://aka.ms/kernelFailuresJupyterTrailtletsOutdated' + ); + }); + + function verifyErrorMessage(message: string, linkInfo?: string) { + message = message.includes('command:jupyter.viewOutput') + ? message + : `${message} \n${DataScience.viewJupyterLogForFurtherInfo()}`; + if (linkInfo) { + verify(applicationShell.showErrorMessage(message, Common.learnMore())).once(); + verify(browser.launch(linkInfo)).once(); + } else { + verify(applicationShell.showErrorMessage(message)).once(); + } } }); });