Skip to content
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

Support external tests, with __init__, WITHOUT forcing src/ layout #4714

Closed
njsmith opened this issue Feb 4, 2019 · 5 comments
Closed

Support external tests, with __init__, WITHOUT forcing src/ layout #4714

njsmith opened this issue Feb 4, 2019 · 5 comments
Labels
type: backward compatibility might present some backward compatibility issues which should be carefully noted in the changelog type: enhancement new feature or API change, should be merged into features branch type: feature-branch new feature or API change, should be merged into features branch

Comments

@njsmith
Copy link

njsmith commented Feb 4, 2019

In the pytest docs, it recommends against the following layout:

setup.py
mypkg/
    ...
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

This is a very convenient layout, so it's unfortunate that it's not supported. The main problem is a technical one. As the docs say:

But now this introduces a subtle problem: in order to load the test modules from the tests directory, pytest prepends the root of the repository to sys.path, which adds the side-effect that now mypkg is also importable.

And this is bad because now you can't test against an installed version of mypkg.

My proposal is: we should fix this technical issue. Pytest should be able to import the tests directory, without the side-effect of making mypkg automatically importable as well.

On Python 3, this is actually trivial to implement. All you have to do is:

from importlib.machinery import PathFinder

@attr.s
class FindTopLevelPackageInDir:
    package = attr.ib()
    dir = attr.ib()

    def find_spec(self, fullname, path=None, target=None):
        if fullname == self.package:
            assert path is target is None
            return PathFinder.find_spec(fullname, path=self.dir)

sys.meta_path.insert(0, FindTopLevelPackageInDir("tests", "/some/path/"))

Now import tests, or import tests.foo, will find our tests in "/some/path", ignoring sys.path. But, import mypkg will continue to search sys.path as normal.

It should be possible on python 2 as well, but I haven't yet stared at PEP 302 for long enough to figure out the details.

Anyway, the question is: given that this is technically possible (and pretty easy at that), is it viable to fix this in pytest, or are there other issues that would block that?

@RonnyPfannschmidt
Copy link
Member

based on timelines i expect to happen we can address this after pytest5.x which will be the last to support python2

this would also be a great chance to move our python import mechanism away from py.path.local and onto pathlib

we still have to take a look at whether this can nicely integrate assertion rewriting

@RonnyPfannschmidt RonnyPfannschmidt added type: enhancement new feature or API change, should be merged into features branch type: backward compatibility might present some backward compatibility issues which should be carefully noted in the changelog type: feature-branch new feature or API change, should be merged into features branch labels Feb 4, 2019
@njsmith
Copy link
Author

njsmith commented Feb 4, 2019

Like I said, I'm pretty sure it is possible to do this on python 2 as well. (In particular, sys.meta_path does exist!) It's just different and more poorly documented, so I figured I'd see how the discussion went here before digging into the details.

...oh, doing a bit more grepping, I see that you're already messing with sys.meta_path in order to support assertion rewriting! It seems likely that you could support both at the same time? You might even be able to keep the two hacks separate – a nice thing about how the import system works is that if you intercept the top-level package, then all future sub-packages get loaded directly from the top-level package's __path__, rather than looking at sys.path, and your assertion rewrite code already knows how to handle that. So if you don't care about rewriting assertions in tests/__init__.py, then you could use one hook to find tests/ and then fall back on your regular hooks for everything after that.

@RonnyPfannschmidt
Copy link
Member

from my pov, im more than happy/willing to make this a python3 only feature

but we do have to take into account nested varied import root situations
as far as i can tel lthe python3 implementation looks really simple, but the fallout it can create on top of more interesting directory layouts is absolutely unclear, i'll take a bit before i can generate a map of the structural pitfalls that i am under the impression may exist here

@njsmith
Copy link
Author

njsmith commented Feb 4, 2019

Yeah, I actually have no idea how pytest's logic for test discovery and path management really works, I always just flail around until something useful happens. So I guess that's my biggest question here, whether this could be slotted into that logic without breaking everything.

st-pasha added a commit to h2oai/datatable that referenced this issue Jan 17, 2020
In this PR we relocate all source files -- both C++ and Python -- into the `/src` directory.
The reason for why this setup is preferred is explained in https://blog.ionelmc.ro/2014/05/25/python-packaging; however for us such move is in fact a necessity: with our previous setup it was impossible for `pytest` to properly test datatable installed from a wheel. Although pytest may eventually fix this problem (pytest-dev/pytest#4714), for now the most reliable solution is to have the `datatable/` folder not in the root of the repository.

As for the `c` folder, there was no good reason to move it, except that now that we have the `src/` folder it seems only logical that the C++ code should also live there.

Also, I've inserted `sys.path.insert(0, "src")` in the tests/__init__.py and tests/conftest.py -- these are temporary changes, designed to ease the transition towards the new file layout.
@bluetech
Copy link
Member

This was implemented in #7246.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: backward compatibility might present some backward compatibility issues which should be carefully noted in the changelog type: enhancement new feature or API change, should be merged into features branch type: feature-branch new feature or API change, should be merged into features branch
Projects
None yet
Development

No branches or pull requests

3 participants