Change pytest rootdir
Asked Answered
F

4

29

I am stuck with this incredibly silly error. I am trying to run pytest on a Raspberry Pi using bluepy.

pi@pi:~/bluepy/bluepy $ pytest test_asdf.py
============================= test session starts       ==============================
platform linux2 -- Python 2.7.9, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /home/pi/bluepy, inifile:
collected 0 items / 1 errors

==================================== ERRORS ====================================
 ______________ ERROR collecting bluepy/test_bluetoothutility.py _______________
ImportError while importing test module '/home/pi/bluepy/bluepy/test_asdf.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
test_asdf:4: in <module>
    from asdf import AsDf
asdf.py:2: in <module>
    from bluepy.btle import *
E       ImportError: No module named btle
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
=========================== 1 error in 0.65 seconds ============================

I realised that my problem could be that rootdir is showing incorrect path. It should be

/home/pi/bluepy/bluepy

I've been reading pytest docs but I just do not get it how to change the rootdir.

Floatfeed answered 24/3, 2017 at 9:53 Comment(1)
It would also be helpful if you could just sketch up a very simple directory structure like I've done below, so that folks know where the files are at relative to one another. In addition, it will be important to know whether the directories are recognized as packages (this just means they all have an __init__.py file in them), since import statements can only be performed on modules in packages and subpackages (i.e., nested packages).Discography
O
23

Your problem is nothing to do with Pytest's rootdir.

The rootdir in Pytest has no connection to how test package names are constructed and rootdir is not added to sys.path, as you can see from the problem you were experiencing. (Beware: the directory that is considered rootdir may be added to the path for other reasons, such as it also being the current working directory when you run python -m pytest.)

The problem here, as others have described, is that the top-level bluepy/ is not in sys.path. The easiest way to handle this if you just want to get something running interactively for yourself is as per Cecil Curry's answer: cd to the top-level bluepy and run Pytest as python -m pytest bluepy/test_asdf.py (or just python -m pytest if you want it to discover all test_* files in or under the current directory and run them). But I think you will need to use python -m pytest, not just pytest, in order to make sure that the current working directory is in the path.

If you're looking to set up a test framework that others can easily run without mysterious failures like this, you'll want to set up a test script that sets the current working directory or PYTHONPATH or whatever appropriately. Or use tox. Or just make this a Python package using standard tools that can run the tests for you. (All that goes way beyond the scope of this question.)

By the way, I concur with Cecil's opinion of Mackie Messer's answer; messing around with conftest.py like that is overly difficult and fragile; there are better solutions for almost any circumstance.

Appendix: Use of rootdir

There are only two things, as far as I'm aware, for which rootdir is used:

  1. The .pytest_cache/ directory is stored in the rootdir unless otherwise specified (with the cache_dir configuration option).
  2. If rootdir contains a conftest.py, it will always be loaded, even if no test files are loaded from in or under the rootdir.

The documentation claims that the rootdir also used to generate nodeids, but adding a conftest.py containing

def pytest_runtest_logstart(nodeid, location):
    print("logstart nodeid={} location={}".format(nodeid, location))            

and running pytest --rootdir=/somewhere/way/outside/the/tree shows that to be incorrect (though node locations are relative to the rootdir).

Organo answered 4/5, 2018 at 8:34 Comment(1)
"If rootdir contains a conftest.py, it will always be loaded, even if no test files are loaded from in or under the rootdir. " a caveat - it seems to be the case that the presence of conftest.py is changing what rootdir is. If it does not exist my pytest is using $PWD as the path.Jaws
D
15

My first guess would be that you don't have that directory in the python path. You can add it to the python path dynamically. One simple way to do this is in a test configuration file conftest.py, which I believe is always executed before test discovery and test running.

For example, you might have a project setup like:

root
+-- tests
|   +-- conftest.py
|   +-- tests_asdf.py
+-- bluepy (or main project dir)
|   +-- miscellaneous modules

In which case, you could add the root dir to your python path in the conftest.py file like so:

#
# conftest.py
import sys
from os.path import dirname as d
from os.path import abspath, join
root_dir = d(d(abspath(__file__)))
sys.path.append(root_dir)

Let me know if that's helpful.

Discography answered 24/3, 2017 at 15:17 Comment(0)
B
3

Actually, py.test is correctly discovering the rootdir for your project to be /home/pi/bluepy. That's good.

Tragically, you are erroneously attempting to run py.test within your project's package subdirectory (i.e., /home/pi/bluepy/bluepy) rather than within your project's rootdir (i.e., /home/pi/bluepy). That's bad.

Let's break this down a little. From within the:

  • /home/pi/bluepy directory, there is a bluepy.btle submodule. (Good.)
  • /home/pi/bluepy/bluepy subdirectory, there is no bluepy.btle submodule. (Bad.) Unless you awkwardly attempt to manually inject the parent directory of this subdirectory (i.e., /home/pi/bluepy) onto sys.path as Makie Messer perhaps inadvisably advises, Python has no means of inferring that the package bluepy actually refers to the current directory coincidentally also named bluepy. To avoid ambiguity issues of this sort, Python is typically only run outside rather than inside of a project's package subdirectory.

Since you ran py.test from the latter rather than the former directory, Python is unable to find the bluepy.btle submodule on the current sys.path. For this and similar reasons, py.test should typically only ever be run from your project's top-level rootdir (i.e., /home/pi/bluepy):

pi@pi:~/ $ cd ~/bluepy pi@pi:~/bluepy $ py.test bluepy/test_asdf.py

Lastly, note that it's typically preferable to defer test discovery to py.test. Rather than explicitly listing all test script filenames on the command line, consider instead letting py.test implicitly find and run all tests containing some substring via the -k option. For example, to run all tests whose function names are prefixed by test_asdf (regardless of the test script they reside in):

pi@pi:~/ $ cd ~/bluepy pi@pi:~/bluepy $ py.test -k test_asdf .

The suffixing . is optional, but often useful. It instructs py.test to set its rootdir property to the current directory (i.e., /home/pi/bluepy). py.test is usually capable of finding your project's rootdir and setting this property on its own, but it can't hurt to specify it manually. (Especially as you're having... issues.)

For further details on rootdir discovery, see Initialization: determining rootdir and inifile in the official py.test documentation.

Bandwidth answered 24/10, 2017 at 6:53 Comment(1)
Perhaps this is overly pedantic, but pytest . does not directly instruct pytest to set the rootdir to .; in fact it has no effect on the rootdir because the current working directory already is used in the calculation regardless of whether . is explicitly specified or not. (The rootdir will still be . even if you run pytest subdir/.) See my answer for more details on what rootdir is really about.Organo
V
0

In latest versions of pytest you can set the pythonpath setting in your pytest config, which forces pytest to add these directories onto the root. Here's an example of how to set it in pyproject.toml

[tool.pytest.ini_options]
addopts = [
    '--strict-config',
    '-ra',
]
pythonpath = [
    'src/lambdas',
    'src/lambdas/data_gen',
]
testpaths = ['src/lambdas/tests']

Vulcanize answered 13/12, 2023 at 17:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.