diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index aef45b60e47..0058ef797d9 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -822,6 +822,10 @@ jobs: GITHUB_TOKEN: ${{ github.token }} shell: bash + - name: Install screen capture dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get install imagemagick x11-xserver-utils + - name: pip install system test requirements run: | python -m pip install --upgrade -r build/venv-smoke-test-requirements.txt @@ -840,3 +844,23 @@ jobs: uses: GabrielBB/xvfb-action@v1.4 with: run: npm run testSmokeLogged + + - name: Upload VS code logs + uses: actions/upload-artifact@v3 + if: failure() + with: + name: VSCodeLogs-Smoke-${{matrix.os}} + path: '${{env.VSC_JUPYTER_USER_DATA_DIR}}/logs/**/*' + retention-days: 1 + + - name: Log test results + if: always() + run: npm run printTestResults + + - name: Upload test result, screenshots files + uses: actions/upload-artifact@v3 + if: always() + with: + name: TestLogs-Smoke-${{matrix.os}} + path: './logs/*' + retention-days: 60 diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index 6291dc4f405..4fd5051430b 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -79,7 +79,7 @@ import { defaultNotebookFormat, isWebExtension } from '../../../platform/common/constants'; -import { dispose } from '../../../platform/common/utils/lifecycle'; +import { dispose, type DisposableStore } from '../../../platform/common/utils/lifecycle'; import { getDisplayPath } from '../../../platform/common/platform/fs-paths'; import { IFileSystem, IPlatformService } from '../../../platform/common/platform/types'; import { GLOBAL_MEMENTO, IDisposable, IMemento } from '../../../platform/common/types'; @@ -970,6 +970,47 @@ export async function waitForExecutionCompletedSuccessfully( await sleep(100); } +export async function waitForExecutionCompletedSuccessfullyV2( + cell: NotebookCell, + disposables: IDisposable[] | DisposableStore +) { + const checkCompletedSuccessfully = () => { + logger.trace( + `10.a Check execution summary: ${cell.index}: Success = ${cell.executionSummary?.success}, order = ${cell.executionSummary?.executionOrder}, End Time = ${cell.executionSummary?.timing?.endTime}` + ); + return cell.executionSummary?.success && + cell.executionSummary.executionOrder && + cell.executionSummary.timing?.endTime + ? true + : false; + }; + logger.trace(`10. Check execution summary: Check ${cell.index}`); + if (checkCompletedSuccessfully()) { + logger.trace(`11. Check execution summary: Success ${cell.index}`); + return; + } + await new Promise((resolve) => { + logger.trace(`12. Check execution summary: Event Handler Added ${cell.index}`); + const disposable = workspace.onDidChangeNotebookDocument((e) => { + if (e.notebook !== cell.notebook) { + logger.trace(`13. Check execution summary: Wrong Notebook ${cell.index}`); + return; + } + logger.trace(`14. Check execution summary: Check ${cell.index}`); + if (checkCompletedSuccessfully()) { + logger.trace(`14. Check execution summary: Resolve ${cell.index}`); + disposable.dispose(); + resolve; + } + }); + if (Array.isArray(disposables)) { + disposables.push(disposable); + } else { + disposables.add(disposable); + } + }); +} + export async function waitForCompletions( completionProvider: CompletionItemProvider, cell: NotebookCell, @@ -1208,6 +1249,45 @@ export async function waitForTextOutput( .join(',\n')}` ); } +export async function waitForTextOutputV2( + cell: NotebookCell, + text: string, + index: number = 0, + isExactMatch = true, + disposables: IDisposable[] | DisposableStore +) { + try { + logger.trace(`2. Check output in cell ${cell.index}`); + assertHasTextOutputInVSCode(cell, text, index, isExactMatch); + logger.trace(`3. Check output in cell: Success ${cell.index}`); + return; + } catch (ex) { + logger.trace(`3.a Check output in cell: Fail ${cell.index}`, ex); + // + } + await new Promise((resolve) => { + logger.trace(`4. Check output in cell: Added EventHandler ${cell.index}`); + const disposable = workspace.onDidChangeNotebookDocument((e) => { + if (e.notebook !== cell.notebook) { + logger.trace(`5. Check output in cell: Wrong Notebook ${cell.index}`); + return; + } + try { + logger.trace(`6. Check output in cell: Event Received ${cell.index}`); + assertHasTextOutputInVSCode(cell, text, index, isExactMatch); + logger.trace(`7. Check output in cell: Resolved ${cell.index}`); + resolve(); + } catch { + // + } + }); + if (Array.isArray(disposables)) { + disposables.push(disposable); + } else { + disposables.add(disposable); + } + }); +} export function assertNotHasTextOutputInVSCode(cell: NotebookCell, text: string, index: number, isExactMatch = true) { const cellOutputs = cell.outputs; assert.ok(cellOutputs, 'No output'); diff --git a/src/test/pythonFiles/datascience/simple_nb.ipynb b/src/test/pythonFiles/datascience/simple_nb.ipynb deleted file mode 100644 index bebbed6c7cd..00000000000 --- a/src/test/pythonFiles/datascience/simple_nb.ipynb +++ /dev/null @@ -1,41 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "with open('ds_n.log', 'a') as fp:\n", - " fp.write('Hello World')\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "nbformat": 4, - "nbformat_minor": 2, - "metadata": { - "language_info": { - "name": "python", - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "version": "3.7.4" - }, - "orig_nbformat": 2, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "npconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": 3 - } -} \ No newline at end of file diff --git a/src/test/smoke/datascience.smoke.test.ts b/src/test/smoke/datascience.smoke.test.ts index 01b156ecfed..89a4e57953a 100644 --- a/src/test/smoke/datascience.smoke.test.ts +++ b/src/test/smoke/datascience.smoke.test.ts @@ -3,17 +3,16 @@ import { assert } from 'chai'; /* eslint-disable , no-invalid-this, @typescript-eslint/no-explicit-any */ -import * as fs from 'fs-extra'; -import * as path from '../../platform/vscode-path/path'; import * as vscode from 'vscode'; import { logger } from '../../platform/logging'; -import { PYTHON_PATH, setAutoSaveDelayInWorkspaceRoot, waitForCondition } from '../common.node'; -import { EXTENSION_ROOT_DIR_FOR_TESTS, IS_SMOKE_TEST, JVSC_EXTENSION_ID_FOR_TESTS } from '../constants.node'; -import { sleep } from '../core'; +import { PYTHON_PATH, waitForCondition } from '../common.node'; +import { IS_SMOKE_TEST, JVSC_EXTENSION_ID_FOR_TESTS } from '../constants.node'; import { closeActiveWindows, initialize, initializeTest } from '../initialize.node'; import { captureScreenShot } from '../common'; import { getCachedEnvironments } from '../../platform/interpreter/helpers'; import { PythonExtension, type EnvironmentPath } from '@vscode/python-extension'; +import { waitForExecutionCompletedSuccessfullyV2, waitForTextOutputV2 } from '../datascience/notebook/helper'; +import { DisposableStore } from '../../platform/common/utils/lifecycle'; type JupyterApi = { openNotebook(uri: vscode.Uri, env: EnvironmentPath): Promise; @@ -22,13 +21,13 @@ type JupyterApi = { const timeoutForCellToRun = 3 * 60 * 1_000; suite('Smoke Tests', function () { this.timeout(timeoutForCellToRun); + const disposableStore = new DisposableStore(); suiteSetup(async function () { this.timeout(timeoutForCellToRun); if (!IS_SMOKE_TEST()) { return this.skip(); } await initialize(); - await setAutoSaveDelayInWorkspaceRoot(1); }); setup(async function () { logger.info(`Start Test ${this.currentTest?.title}`); @@ -37,6 +36,7 @@ suite('Smoke Tests', function () { }); suiteTeardown(closeActiveWindows); teardown(async function () { + disposableStore.clear(); logger.info(`End Test ${this.currentTest?.title}`); if (this.currentTest?.isFailed()) { await captureScreenShot(this); @@ -73,48 +73,29 @@ suite('Smoke Tests', function () { // }).timeout(timeoutForCellToRun); test('Run Cell in Notebook', async function () { - const file = path.join( - EXTENSION_ROOT_DIR_FOR_TESTS, - 'src', - 'test', - 'pythonFiles', - 'datascience', - 'simple_nb.ipynb' - ); - const fileContents = await fs.readFile(file, { encoding: 'utf-8' }); - const outputFile = path.join(path.dirname(file), 'ds_n.log'); - await fs.writeFile(file, fileContents.replace("'ds_n.log'", `'${outputFile.replace(/\\/g, '/')}'`), { - encoding: 'utf-8' - }); - if (await fs.pathExists(outputFile)) { - await fs.unlink(outputFile); - } - logger.info(`Opening notebook file ${file}`); - const notebook = await vscode.workspace.openNotebookDocument(vscode.Uri.file(file)); - await vscode.window.showNotebookDocument(notebook); - - let pythonPath = PYTHON_PATH; - const nb = vscode.window.activeNotebookEditor?.notebook; - if (!nb) { - throw new Error('No active notebook'); - } - const pythonEnv = await PythonExtension.api().then((api) => api.environments.resolveEnvironment(pythonPath)); - if (!pythonEnv) { - throw new Error(`Python environment not found ${pythonPath}`); - } const jupyterExt = vscode.extensions.getExtension(JVSC_EXTENSION_ID_FOR_TESTS); if (!jupyterExt) { throw new Error('Jupyter extension not found'); } - await jupyterExt?.activate(); - await jupyterExt.exports.openNotebook(nb.uri, pythonEnv); - - await vscode.commands.executeCommand('notebook.execute'); - const checkIfFileHasBeenCreated = () => fs.pathExists(outputFile); - await waitForCondition(checkIfFileHasBeenCreated, timeoutForCellToRun, `"${outputFile}" file not created`); + const cellData = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print("Hello World")', 'python'); + const [pythonEnv, { notebook }] = await Promise.all([ + PythonExtension.api().then((api) => api.environments.resolveEnvironment(PYTHON_PATH)), + vscode.workspace + .openNotebookDocument('jupyter-notebook', new vscode.NotebookData([cellData])) + .then((notebook) => vscode.window.showNotebookDocument(notebook)), + jupyterExt.activate() + ]); - // Give time for the file to be saved before we shutdown - await sleep(300); + if (!pythonEnv) { + throw new Error(`Python environment not found ${PYTHON_PATH}`); + } + await jupyterExt.exports.openNotebook(notebook.uri, pythonEnv); + logger.trace(`1. Notebook Opened`); + await Promise.all([ + vscode.commands.executeCommand('notebook.execute'), + waitForTextOutputV2(notebook.cellAt(0), 'Hello World', 0, false, disposableStore), + waitForExecutionCompletedSuccessfullyV2(notebook.cellAt(0), disposableStore) + ]); }).timeout(timeoutForCellToRun); test('Interactive window should always pick up current active interpreter', async function () {