-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Fix determining rootdir from common_ancestor #1621
Fix determining rootdir from common_ancestor #1621
Conversation
@@ -1136,7 +1140,11 @@ def determine_setup(inifile, args): | |||
if rootdir.join("setup.py").exists(): | |||
break | |||
else: | |||
rootdir = ancestor | |||
dirs = get_dirs_from_args(args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this actually. If we pass args
directly into getcfg
, they are validated as paths
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be used in other places as well - at least that's what I have locally at the moment.
Here's my documentation updates: diff --git a/doc/en/customize.rst b/doc/en/customize.rst
index 34e319c..a8e6809 100644
--- a/doc/en/customize.rst
+++ b/doc/en/customize.rst
@@ -29,25 +29,29 @@ project/testrun-specific information.
Here is the algorithm which finds the rootdir from ``args``:
-- determine the common ancestor directory for the specified ``args``.
+- determine the common ancestor directory for the specified ``args`` that are
+ recognised as paths that exist in the file system. If no such paths are
+ found, the common ancestor directory is set to the current working directory.
-- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the
- ancestor directory and upwards. If one is matched, it becomes the
- ini-file and its directory becomes the rootdir. An existing
- ``pytest.ini`` file will always be considered a match whereas
- ``tox.ini`` and ``setup.cfg`` will only match if they contain
- a ``[pytest]`` section.
+- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor
+ directory and upwards. If one is matched, it becomes the ini-file and its
+ directory becomes the rootdir.
-- if no ini-file was found, look for ``setup.py`` upwards from
- the common ancestor directory to determine the ``rootdir``.
+- if no ini-file was found, look for ``setup.py`` upwards from the common
+ ancestor directory to determine the ``rootdir``.
-- if no ini-file and no ``setup.py`` was found, use the already
- determined common ancestor as root directory. This allows to
- work with pytest in structures that are not part of a package
- and don't have any particular ini-file configuration.
+- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and
+ ``setup.cfg`` in each of the specified ``args`` and upwards. If one is
+ matched, it becomes the ini-file and its directory becomes the rootdir.
-Note that options from multiple ini-files candidates are never merged,
-the first one wins (``pytest.ini`` always wins even if it does not
+- if no ini-file was found, use the already determined common ancestor as root
+ directory. This allows to work with pytest in structures that are not part of
+ a package and don't have any particular ini-file configuration.
+
+Note that an existing ``pytest.ini`` file will always be considered a match,
+whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a
+``[pytest]`` section. Options from multiple ini-files candidates are never
+merged - the first one wins (``pytest.ini`` always wins, even if it does not
contain a ``[pytest]`` section).
The ``config`` object will subsequently carry these attributes: I can push to your fork if you add me as a contributor, or I can push to my fork for you to cherry-pick this change. Or, you can use the patch from above. |
Guys, how is it going? Make sure to remove "WIP" from the PR's title once this is ready. 😉 |
@nicoddemus I've added the documentation patch and pushed some other (hopefully rather non-breaking fixup). |
@blueyed I suggest we sit together to take look. |
Given the following directory structure:
Before this pull request you get: $ cd /tmp/testdir
$ PYTHONPATH=hello:world py.test -v --pyargs ns_pkg.hello world/ns_pkg
===================================== test session starts ======================================
platform darwin -- Python 2.7.11, pytest-2.9.3.dev0, py-1.4.31, pluggy-0.3.1
cachedir: .cache
rootdir: /tmp/testdir, inifile:
collected 4 items
hello/ns_pkg/hello/test_hello.py::test_hello PASSED
hello/ns_pkg/hello/test_hello.py::test_other PASSED
world/ns_pkg/world/test_world.py::test_world PASSED
world/ns_pkg/world/test_world.py::test_other PASSED
=================================== 4 passed in 0.12 seconds ===================================
With your PR you get: $ cd /tmp/testdir
$ PYTHONPATH=hello:world py.test -v --pyargs ns_pkg.hello world/ns_pkg
============================= test session starts ==============================
platform darwin -- Python 2.7.11, pytest-2.9.3.dev0, py-1.4.31, pluggy-0.3.1
cachedir: world/ns_pkg/.cache
rootdir: /tmp/testdir/world/ns_pkg, inifile:
collected 4 items
world/ns_pkg/::test_hello <- ../../hello/ns_pkg/hello/test_hello.py PASSED
world/ns_pkg/::test_other <- ../../hello/ns_pkg/hello/test_hello.py PASSED
world/ns_pkg/world/test_world.py::test_world PASSED
world/ns_pkg/world/test_world.py::test_other PASSED
=========================== 4 passed in 0.02 seconds ===========================
Remarks:
|
For your convenience, here's the tar file of the test directory above. |
Thanks @taschini for the detailed analysis! 😁 We're at the Pytest sprint at the moment, I will sit together with @blueyed later, but I my first impression is that we will change the test slightly to create a |
I haven't looked at the source code of this PR, but from what I gather from the docs by @davehunt, there might be a little problem with the algorithm to determine the common ancestor. As far as I can see, in pseudo-code it works as follows: paths = [arg for arg in args if os.path.exists(arg)]
common_ancestor = common_prefix(paths or ['.']) It might be better to have something like paths = [arg if os.path.exists(arg) else '.' for arg in args]
common_ancestor = common_prefix(paths or ['.']) That is to say, instead of ignoring arguments that are not recognised as paths that exist in the file system, one might be better off to map them to the cwd. |
2ceea07
to
9b023c9
Compare
@taschini The "use-cwd-as-fallback" was a good idea, which I've incorporated into the latest fixup: the documentation needs to be revised probably now. I'll finish this tomorrow/tonight. |
c1ea4b3
to
01b53cd
Compare
@blueyed can this be merged, or are there still work to be done here? |
@nicoddemus Then the commits need to be squashed also, of course - I've left it as-is for now to see where we've gone through. |
dirs, ["pytest.ini", "tox.ini", "setup.cfg"]) | ||
if rootdir is None: | ||
rootdir = get_common_ancestor([py.path.local(), ancestor]) | ||
if os.path.splitdrive(str(rootdir))[1] == os.sep: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest this to be moved to a local variable to improve legibility:
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
if is_fs_root:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
LGTM, but I would like a second set of yes to take a look as well.
Sounds good. I think you could that already when you got the time. 😁 |
Also, you could remove |
f815b9c
to
53cd1ec
Compare
00d90da
to
8261801
Compare
Let's not forget to add a CHANGELOG entry before merging. 😁 |
@nicoddemus |
Sounds good, thanks! 👍 |
@RonnyPfannschmidt |
I'm happy if @blueyed is happy. Looking forward to having this in 3.0! |
common_ancestor = common_ancestor.dirpath() | ||
return common_ancestor | ||
|
||
def get_dirs_from_args(args): | ||
return [d for d in (py.path.local(x) for x in args if not | ||
str(x).startswith("-")) if d.exists()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatting of this makes it pretty hard to read. Could d for d in
be aligned with if d.exists()
more clearly?
Would splitting into two statements, or something like this be better:?
return [d for d in (py.path.local(x) for x in args if not
str(x).startswith("-"))
if d.exists()]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomviner
Thanks, applied - also wrapped the first if
.
@blueyed the only thing missing here is the CHANGELOG? If so I can write the CHANGELOG myself if you are short on time. 😁 |
8261801
to
eb08135
Compare
@nicoddemus |
Thanks guys, very much appreciated! |
TODO: