Repository navigation
Debugging unittest tests silently reports "skipped" when -s points at a subdirectory #26155
Description
Type: Bug
(apologies if this should be in the debugger extenions issues, raising here as it appeared to originate in the python extensions code)
Behaviour
With python tests configured to use unittest and settings a custom --start-directory set to a sub dir (rather than just '.') I found that RUNNING tests from the GUI works fine, but DEBUGGING them does not. For the latter tests just report as skipped, and the results pannel reads "The test case did not report any output.".
Test discovery itself is unaffected - the test tree is populated correctly & running tests works. It is only debug execution that fails.
The Python output channel shows the run path completing normally and the debug path
tearing down its results pipe without ever receiving data:
RUN 12:04:20.034 Running unittest with arguments: ...execution.py --udiscovery -v -s ./test -p test_*.py
12:04:24.099 Test Result named pipe ... received data <- works
DEBUG 12:04:30.381 Running DEBUG unittest ... execution.py,--udiscovery,-v,-s,./test,-p,test_*.py
12:04:40.428 [test-debug] Debug session started: Debug Tests: python (599b91eb...)
12:04:43.532 [test-debug] Debug session terminated: (599b91eb...)
12:04:43.532 Test Result named pipe ... cancelled <- no data
Steps to reproduce:
- A project whose tests live in an importable package below the workspace root, eg
test/__init__.py,test/core/__init__.py,test/core/test_thing.py. .vscode/settings.json:{ "python.testing.unittestEnabled": true, "python.testing.pytestEnabled": false, "python.testing.unittestArgs": ["-v", "-s", "./test", "-p", "test_*.py"] }- Discover tests, then pick any test and Debug Test.
- The test reports as "skipped"; no output anywhere. Run Test on the same test works.
Not reproducible when -s is omitted or is .. My AI reckons this means #26033 may be related.
Workaround
Add -t . (aka --top-level-directory .) to python.testing.unittestArgs and refresh test discovery.
"python.testing.unittestArgs": ["-v", "-s", "./test", "-t", ".", "-p", "test_*.py"]AI identified potential cause & solution:
I got my AI to poke around in order to find the workaround above, during which it believes it's identified the issue and a potential fix, i've included them in case they're useful. Apologies if they're just noise - it looks sensible to me but I'm not familiar enough with the details to know.
AI output
1. The debug launch config never receives PROJECT_ROOT_PATH
The unittest execution adapter sets PYTHONPATH, TEST_RUN_PIPE and
PROJECT_ROOT_PATH for the run path. The debug path, in convertConfigToArgs
(inferred to be src/client/testing/common/debugLauncher.ts; quoted from the shipped
out/client/extension.js, beautified), injects only two of them:
p.env = {
...p.env,
TEST_RUN_PIPE: n.pytestPort,
RUN_TEST_IDS_PIPE: n.runTestIdsPort
};
const d = u.join(h.EXTENSION_ROOT_DIR, "python_files");
if (p.env) {
const e = [d];
p.cwd ? e.push(p.cwd) : n.cwd && e.push(n.cwd), (0, T.addPathToPythonpath)(e, p.env.PYTHONPATH)
}Two defects here:
PROJECT_ROOT_PATHis never added.addPathToPythonpath(...)is a pure function that returns the joined path, but its
return value is discarded, soPYTHONPATHis never set either.
Only the first causes the symptom reported here. execution.py inserts the resolved cwd
onto sys.path itself (L213-217), so imports still resolve without PYTHONPATH.
Verified by reading the resolved LaunchRequest from a debugpy log
(DEBUGPY_LOG_DIR): the debuggee's env contains RUN_TEST_IDS_PIPE and
TEST_RUN_PIPE but neither PROJECT_ROOT_PATH nor PYTHONPATH. Confirmed across
three separate debug sessions.
2. Discovery and execution then disagree on top_level_dir
python_files/unittestadapter/discovery.py (L144-150) overrides top_level_dir with
PROJECT_ROOT_PATH:
project_root_path = os.environ.get("PROJECT_ROOT_PATH")
if project_root_path:
top_level_dir = project_root_pathpython_files/unittestadapter/execution.py does not - it uses -t (default None, per
pvsc_utils.parse_unittest_args) and additionally replaces start_dir with the resolved
cwd:
cwd = os.path.abspath(project_root_path or start_dir)
...
start_dir = cwd
suite = loader.discover(start_dir, pattern, top_level_dir)So for -s ./test with no -t:
| phase | PROJECT_ROOT_PATH |
discovery root | resulting test id |
|---|---|---|---|
| discovery | set | workspace | test.core.test_thing.TestThing.test_x |
| execution (run) | set | workspace | test.core.test_thing.TestThing.test_x |
| execution (debug) | absent | <workspace>/test |
core.test_thing.TestThing.test_x |
3. A mismatch produces silence rather than an error
execution.py's filter_tests matches on exact test.id() strings, and results are
streamed per test via UnittestTestResult -> send_run_data -> send_post_request. An
empty filtered suite therefore sends nothing at all, which is why the results pipe is
cancelled with no data and the test renders as "skipped" rather than as an error.
4. Why it is undiagnosable from the UI
The auto-generated test debug configuration resolves to console: internalConsole with
debugOptions: ["Multiprocess", "FixFilePathCase", "ShowReturnValue"] and no
RedirectOutput, so the adapter's stdout and stderr are discarded. Nothing appears in
the Debug Console, Terminal or Test Results. Adding "redirectOutput": true to a
launch.json config with "purpose": ["debug-test"] restores output.
Suggested fix
- Propagate
PROJECT_ROOT_PATHinto the debug configuration'senvalongside
TEST_RUN_PIPE/RUN_TEST_IDS_PIPE. - Assign the result of
addPathToPythonpath(...)back top.env.PYTHONPATH. - Consider having
execution.pyhonourPROJECT_ROOT_PATHastop_level_dirthe way
discovery.pydoes, so the two phases cannot disagree regardless of how the process
is launched. - Consider defaulting
redirectOutputtotruefor test debug sessions - the silence
is what made this expensive to diagnose.
Note that (3) interacts with #26033, which is the opposite manifestation of the same
PROJECT_ROOT_PATH/-t coupling introduced by #25780: there, PROJECT_ROOT_PATH
overriding -t breaks discovery when the start directory is not importable from the
workspace root. A fix should probably reconcile both - honour an explicit -t when
given, and otherwise apply PROJECT_ROOT_PATH consistently in both phases and both
launch paths.
Application info:
Extension version: 2026.4.0
VS Code version: Code 1.113.0 (cfbea10c5ffb233ea9177d34726e6056e89913dc, 2026-03-24T15:07:18+01:00)
OS version: Windows_NT x64 10.0.26100
- Python version: 3.13.11
- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): venv
- Value of the
python.languageServersetting: Default
User Settings
languageServer: "Pylance"
testing
• unittestArgs: ["-v", "-s", "./test", "-p", "test_*.py"]
• unittestEnabled: true
Installed Extensions
| Extension Name | Extension Id | Version |
|---|---|---|
| cmake-tools | ms- | 1.23.52 |
| code-spell-checker | str | 4.5.6 |
| code-spell-checker-cspell-bundled-dictionaries | str | 2.0.14 |
| cpp-devtools | ms- | 0.5.13 |
| cpptools | ms- | 1.32.2 |
| cpptools-extension-pack | ms- | 1.5.1 |
| cpptools-themes | ms- | 2.0.0 |
| debugpy | ms- | 2026.6.0 |
| even-better-toml | tam | 0.21.2 |
| js-debug | ms- | 1.112.0 |
| js-debug-companion | ms- | 1.1.3 |
| jupyter | ms- | 2025.9.1 |
| jupyter-keymap | ms- | 1.1.2 |
| jupyter-renderers | ms- | 1.3.0 |
| markdown-mermaid | bie | 1.32.1 |
| python | ms- | 2026.4.0 |
| remote-explorer | ms- | 0.5.0 |
| remote-ssh | ms- | 0.124.0 |
| remote-ssh-edit | ms- | 0.87.0 |
| ruff | cha | 2026.60.0 |
| vscode-clangd | llv | 0.6.0 |
| vscode-js-profile-table | ms- | 1.0.10 |
| vscode-jupyter-cell-tags | ms- | 0.1.9 |
| vscode-jupyter-slideshow | ms- | 0.1.6 |
| vscode-pylance | ms- | 2026.2.1 |
| vscode-python-envs | ms- | 1.36.0 |
| vscode-xml | red | 0.29.3 |
Activity