Skip to content

unittest.TestLoader.discover() leaves _top_level_dir set if discovery fails #158377

Description

@christianaurichzm

Bug report

Bug description:

TestLoader.discover() saves self._top_level_dir on entry and restores it at the end of the call (gh-59215), but only when discovery succeeds. If discovery raises, for example because the start directory is not importable, the loader keeps the top-level directory of the failed call. A later discover() on the same loader without an explicit top_level_dir then resolves against it and fails:

import os
import tempfile
import unittest

with tempfile.TemporaryDirectory() as tmp:
    tests = os.path.join(tmp, "tests")
    os.mkdir(tests)
    with open(os.path.join(tests, "test_x.py"), "w") as f:
        f.write("import unittest\n"
                "class T(unittest.TestCase):\n"
                "    def test_ok(self): pass\n")

    loader = unittest.TestLoader()
    try:
        loader.discover(os.path.join(tmp, "tset"))  # typo, no such directory
    except ImportError as e:
        print(e)
    print(loader._top_level_dir)  # .../tset, expected None

    print(loader.discover(tests).countTestCases())  # ImportError, expected 1
Start directory is not importable: '/tmp/tmpXXXX/tset'
/tmp/tmpXXXX/tset
Traceback (most recent call last):
  ...
ImportError: Start directory is not importable: '/tmp/tmpXXXX/tests'

The same second call on a fresh TestLoader finds the test. Since unittest.defaultTestLoader is a module-level instance, one failed discovery through it breaks later discoveries that do not pass top_level_dir.

Restoring _top_level_dir in a finally block would fix this.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions