Skip to content

Run debug tests multiple times and report debugpy logs #11338

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

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
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
8 changes: 8 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,14 @@ jobs:
path: './logs/*'
retention-days: 60

- name: Upload debugpy logs
uses: actions/upload-artifact@v3
if: always()
with:
name: debugpy-${{matrix.jupyter}}-${{matrix.python}}-${{matrix.pythonVersion}}-${{matrix.packageVersion}}-${{matrix.webOrDesktop}}-${{matrix.os}}
path: debugpyLogs/*
retention-days: 60

- name: Verify there are no unhandled errors
run: npm run verifyUnhandledErrors

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ src/test/datascience/.venv*
*.xlf
*.nls.*.json
*.i18n.json
debugpyLogs
2 changes: 1 addition & 1 deletion build/webTestReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ exports.dumpTestSummary = async () => {
} else if (passedCount < 1) {
// Temporarily reduced to 1 since #11917 disabled tests
// the non-python suite only has 4 tests passing currently, so that's the highest bar we can use.
core.setFailed('Not enough tests were run - are too many being skipped?');
// core.setFailed('Not enough tests were run - are too many being skipped?');
}

// Write output into an ipynb file with the failures & corresponding console output & screenshot.
Expand Down
11 changes: 8 additions & 3 deletions pythonFiles/install_debugpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python")
DEBUGGER_PACKAGE = "debugpy"
DEBUGGER_PYTHON_ABI_VERSIONS = ("cp37",)
DEBUGGER_VERSION = "latest" # can also be "latest"
DEBUGGER_VERSION = "dev" # can also be "latest"


def _contains(s, parts=()):
Expand All @@ -34,7 +34,7 @@ def _get_debugger_wheel_urls(data, version):
)


def _download_and_extract(root, url, version):
def _download_and_extract(root, url):
root = os.getcwd() if root is None or root == "." else root
builtins.print(url)
with url_lib.urlopen(url) as response:
Expand All @@ -49,6 +49,11 @@ def _download_and_extract(root, url, version):


def main(root):
if DEBUGGER_VERSION == "dev":
_download_and_extract(root, "https://github.com/microsoft/debugpy/archive/refs/heads/main.zip")
os.rename(os.path.join(root, "debugpy-main/src/debugpy"), os.path.join(root, "debugpy"))
return

data = _get_package_data()

if DEBUGGER_VERSION == "latest":
Expand All @@ -57,7 +62,7 @@ def main(root):
use_version = DEBUGGER_VERSION

for url in _get_debugger_wheel_urls(data, use_version):
_download_and_extract(root, url, use_version)
_download_and_extract(root, url)


if __name__ == "__main__":
Expand Down
70 changes: 52 additions & 18 deletions src/test/datascience/debugger.vscode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
'use strict';
import { assert } from 'chai';
import * as fs from 'fs';
import glob from 'glob';
import * as os from 'os';
import * as sinon from 'sinon';
import { commands, debug } from 'vscode';
Expand All @@ -16,10 +17,10 @@ import { IDisposable } from '../../platform/common/types';
import { isWeb } from '../../platform/common/utils/misc';
import { traceInfo } from '../../platform/logging';
import * as path from '../../platform/vscode-path/path';
import { IVariableViewProvider } from '../../webviews/extension-side/variablesView/types';
import { captureScreenShot, IExtensionTestApi, waitForCondition } from '../common.node';
import { noop, sleep } from '../core';
import { initialize, IS_REMOTE_NATIVE_TEST } from '../initialize.node';
import { testN } from '../utils/tests';
import {
closeNotebooks,
closeNotebooksAndCleanUpAfterTests,
Expand All @@ -32,15 +33,48 @@ import {
runCell,
waitForStoppedEvent
} from './notebook/helper.node';
import { ITestWebviewHost } from './testInterfaces';
import { waitForVariablesToMatch } from './variableView/variableViewHelpers';
import { ITestVariableViewProvider } from './variableView/variableViewTestInterfaces';

suite('Run By Line @debugger', function () {
const N = 100;

function processDebugpyLogs(testName: string, _state?: 'passed' | 'failed'): Promise<void> {
// eslint-disable-next-line local-rules/dont-use-process
const logDir = process.env.DEBUGPY_LOG_DIR;

return new Promise((resolve, reject) => {
glob(
'debugpy.*',
// eslint-disable-next-line local-rules/dont-use-process
{ cwd: logDir },
(err, files) => {
if (err) {
reject(err);
}

if (!logDir) {
reject(new Error('No debugpy log dir'));
return;
}

// if (state === 'passed') {
// files.forEach((file) => fs.rmSync(path.join(logDir, file)));
// resolve();
// return;
// }

files.forEach((file) =>
fs.renameSync(path.join(logDir, file), path.join(logDir, `${testName}-${file}`))
);
resolve();
}
);
});
}

// eslint-disable-next-line no-only-tests/no-only-tests
suite.only('Run By Line @debugger', function () {
let api: IExtensionTestApi;
const disposables: IDisposable[] = [];
let commandManager: ICommandManager;
let variableViewProvider: ITestVariableViewProvider;
let vscodeNotebook: IVSCodeNotebook;
let debuggingManager: IDebuggingManager;
this.timeout(120_000);
Expand All @@ -57,9 +91,6 @@ suite('Run By Line @debugger', function () {
await prewarmNotebooks();
sinon.restore();
commandManager = api.serviceContainer.get<ICommandManager>(ICommandManager);
const coreVariableViewProvider = api.serviceContainer.get<IVariableViewProvider>(IVariableViewProvider);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
variableViewProvider = coreVariableViewProvider as any as ITestVariableViewProvider; // Cast to expose the test interfaces
debuggingManager = api.serviceContainer.get<IDebuggingManager>(INotebookDebuggingManager);
vscodeNotebook = api.serviceContainer.get<IVSCodeNotebook>(IVSCodeNotebook);
traceInfo(`Start Test Suite (completed)`);
Expand Down Expand Up @@ -92,6 +123,9 @@ suite('Run By Line @debugger', function () {
}
await closeNotebooks(disposables);
await closeNotebooksAndCleanUpAfterTests(disposables);
if (this.currentTest) {
await processDebugpyLogs(this.currentTest.title.replace(/ /g, '_'), this.currentTest.state);
}
traceInfo(`Ended Test (completed) ${this.currentTest?.title}`);
});

Expand Down Expand Up @@ -145,7 +179,7 @@ suite('Run By Line @debugger', function () {
}
});

test.skip('Stops at end of cell', async function () {
testN('Stops at end of cell', N, async function () {
// Run by line seems to end up on the second line of the function, not the first
const cell = await insertCodeCell('a=1\na', { index: 0 });
const doc = vscodeNotebook.activeNotebookEditor?.notebook!;
Expand All @@ -163,15 +197,15 @@ suite('Run By Line @debugger', function () {
assert.equal(stack.stackFrames[0].source?.path, cell.document.uri.toString(), 'Stopped at the wrong path');
traceInfo(`Got past first stop event`);

const coreVariableView = await variableViewProvider.activeVariableView;
const variableView = coreVariableView as unknown as ITestWebviewHost;
// const coreVariableView = await variableViewProvider.activeVariableView;
// const variableView = coreVariableView as unknown as ITestWebviewHost;

await commandManager.executeCommand(Commands.RunByLineNext, cell);
await waitForStoppedEvent(debugAdapter!);
traceInfo(`Got past second stop event`);

const expectedVariables = [{ name: 'a', type: 'int', length: '', value: '1' }];
await waitForVariablesToMatch(expectedVariables, variableView);
// const expectedVariables = [{ name: 'a', type: 'int', length: '', value: '1' }];
// await waitForVariablesToMatch(expectedVariables, variableView);

await commandManager.executeCommand(Commands.RunByLineNext, cell);
await waitForCondition(
Expand All @@ -189,7 +223,7 @@ suite('Run By Line @debugger', function () {
assert.isTrue(getCellOutputs(cell).includes('1'));
});

test('Interrupt during debugging', async function () {
test.skip('Interrupt during debugging', async function () {
const cell = await insertCodeCell('a=1\na', { index: 0 });
const doc = vscodeNotebook.activeNotebookEditor?.notebook!;

Expand All @@ -207,7 +241,7 @@ suite('Run By Line @debugger', function () {
);
});

test('Stops in same-cell function called from last line', async function () {
test.skip('Stops in same-cell function called from last line', async function () {
const cell = await insertCodeCell('def foo():\n print(1)\n\nfoo()', { index: 0 });
const doc = vscodeNotebook.activeNotebookEditor?.notebook!;

Expand Down Expand Up @@ -258,7 +292,7 @@ suite('Run By Line @debugger', function () {
assert.equal(stack.stackFrames[0].line, 1, 'Stopped at the wrong line');
});

test.skip('Does not stop in other cell', async function () {
testN('Does not stop in other cell', N, async function () {
// https://github.com/microsoft/vscode-jupyter/issues/8757
const cell0 = await insertCodeCell('def foo():\n print(1)');
const cell1 = await insertCodeCell('foo()');
Expand All @@ -281,7 +315,7 @@ suite('Run By Line @debugger', function () {
);
});

test.skip('Run a second time after interrupt', async function () {
testN('Run a second time after interrupt', N, async function () {
// https://github.com/microsoft/vscode-jupyter/issues/11245
await insertCodeCell(
'import time\nfor i in range(0,50):\n time.sleep(.1)\n print("sleepy")\nprint("final " + "output")',
Expand Down
5 changes: 4 additions & 1 deletion src/test/standardTest.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ async function start() {
const userDataDirectory = await createSettings();
const extensionsDir = await getExtensionsDir();
await installPythonExtension(vscodeExecutablePath, extensionsDir, platform);
const debugpyLogDir = path.resolve(__dirname, '..', '..', 'debugpyLogs');
fs.mkdirpSync(debugpyLogDir);
console.log(`debugpy log dir: ${debugpyLogDir}`);
await runTests({
vscodeExecutablePath,
extensionDevelopmentPath: extensionDevelopmentPath,
Expand All @@ -157,7 +160,7 @@ async function start() {
.concat(['--user-data-dir', userDataDirectory]),
// .concat(['--verbose']), // Too much logging from VS Code, enable this to see what's going on in VSC.
version: channel,
extensionTestsEnv: { ...process.env, DISABLE_INSIDERS_EXTENSION: '1' }
extensionTestsEnv: { ...process.env, DISABLE_INSIDERS_EXTENSION: '1', DEBUGPY_LOG_DIR: debugpyLogDir }
});
}
start().catch((ex) => {
Expand Down
8 changes: 8 additions & 0 deletions src/test/utils/tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

export function testN(name: string, n: number, fn: () => unknown): void {
for (let i = 0; i < n; i += 1) {
test(`${name} - ${i + 1}`, fn);
}
}