This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: unittest discover throws TypeError on namespace packages
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: augustogoulart, barry, brett.cannon, eric.smith, ezio.melotti, methane, michael.foord, rbcollins, xtreak
Priority: normal Keywords: patch

Created on 2019-05-02 16:47 by xtreak, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 13259 open augustogoulart, 2019-05-12 02:57
Messages (5)
msg341285 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-02 16:47
In the given folder structure unittest discover fails. I think this is due to issue32303 where __file__ was set as None and I guess empty packages didn't have __file__ set before which has None value now. Hence the_module.__file__ returns None and thus subsequent calls to os.path.dirname raise TypeError. There is one another location in the same file in unittest module causing this issue. See also issue36406 for similar error in doctest with the change. I reverted bbbcf8693b876daae4469765aa62f8924f39a7d2 just to confirm the issue. I am adding devs in issue32303 and unittest. Any thoughts on this would be helpful.

➜  cpython git:(master) ✗ tree test1
test1
└── test2
    └── test_foo.py

1 directory, 1 file
➜  cpython git:(master) ✗ ./python.exe -m unittest discover test1.test2
Traceback (most recent call last):
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/runpy.py", line 192, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/__main__.py", line 18, in <module>
    main(module=None)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/main.py", line 100, in __init__
    self.parseArgs(argv)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/main.py", line 124, in parseArgs
    self._do_discovery(argv[2:])
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/main.py", line 244, in _do_discovery
    self.createTests(from_discovery=True, Loader=Loader)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/main.py", line 154, in createTests
    self.test = loader.discover(self.start, self.pattern, self.top)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/loader.py", line 306, in discover
    os.path.dirname((the_module.__file__)))
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/posixpath.py", line 152, in dirname
    p = os.fspath(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType

# 3.6.4 works that doesn't have the change

➜  cpython git:(master) ✗ python3.6 -m unittest discover test1.test2
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK


A unittest would be as below : 

def test_empty_package_discovery(self):
    # bpo-36789: Return zero test cases when using discovery in
    # empty packages.

    with support.temp_dir() as path:
        dirname, basename = os.path.split(path)
        os.mkdir(os.path.join(path, 'test2'))

        with support.DirsOnSysPath(dirname):
            loader = unittest.TestLoader()
            empty_package = f"{basename}.test2"
            tests_count = loader.discover(empty_package).countTestCases()
            self.assertEqual(loader.discover(empty_package).countTestCases(), 0)


One possible fix would be to check for __file__ == None and return empty list of test cases but this causes a behavior change like below where my patch returns 0 and python 3.6 returns 1 test.

# Patch

diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py
index ba7105e1ad..f465b2419f 100644
--- a/Lib/unittest/loader.py
+++ b/Lib/unittest/loader.py
@@ -302,6 +302,10 @@ class TestLoader(object):
                 the_module = sys.modules[start_dir]
                 top_part = start_dir.split('.')[0]
                 try:
+                    filepath = the_module.__file__
+                    # __file__ is None for empty packages. Hence return empty list of tests.
+                    if filepath == None:
+                        return self.suiteClass([])
                     start_dir = os.path.abspath(
                        os.path.dirname((the_module.__file__)))
                 except AttributeError:

# Behavior change

➜  cpython git:(unittest-empty-package) ✗ ./python.exe -m unittest discover test1.test2

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK
➜  cpython git:(unittest-empty-package) ✗ python3.6 -m unittest discover test1.test2
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

My patch also causes reference leak and I am not sure why. So I guess mine is not the best approach to solving this.

➜  cpython git:(unittest-empty-package) ✗ ./python.exe -m test --fail-env-changed -R 3:3 test_unittest
Run tests sequentially
0:00:00 load avg: 1.46 [1/1] test_unittest
beginning 6 repetitions
123456
......
test_unittest leaked [99, 99, 99] references, sum=297
test_unittest leaked [38, 38, 38] memory blocks, sum=114
test_unittest failed in 43 sec 634 ms

== Tests result: FAILURE ==

1 test failed:
    test_unittest

Total duration: 43 sec 655 ms
Tests result: FAILURE
msg341339 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-03 14:04
I opened issue36784 for reference leak in the initial report along with a reproducer that is reproducible on master branch without my changes.
msg341530 - (view) Author: Augusto Goulart (augustogoulart) * Date: 2019-05-06 15:29
Confirming that the issue seems to have been introduced on 3.7. Working on this at the PyCon US 2019 sprints.
msg373941 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2020-07-19 08:06
I didn't know packages without __init__.py are namespace packages back then and used empty packages. I guess it's related to the other issues with unittest and namespace packages issue36723, issue23882 and issue35617
msg373980 - (view) Author: Inada Naoki (methane) * (Python committer) Date: 2020-07-20 03:33
This issue is not duplicate of #23882.
In the example, test1 and test1.test2 are namespace package. But they are specified explicitly.

`./python.exe -m unittest discover test1.test2`
History
Date User Action Args
2022-04-11 14:59:14adminsetgithub: 80958
2020-07-20 03:33:05methanesetmessages: + msg373980
2020-07-19 08:06:48xtreaksetnosy: + methane

messages: + msg373941
title: unittest discover throws TypeError on empty packages -> unittest discover throws TypeError on namespace packages
2019-05-12 02:57:34augustogoulartsetkeywords: + patch
stage: patch review
pull_requests: + pull_request13169
2019-05-06 15:29:48augustogoulartsetnosy: + augustogoulart
messages: + msg341530
2019-05-03 14:04:33xtreaksetmessages: + msg341339
2019-05-02 16:47:03xtreakcreate