-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Port test suite to pytest #321
Conversation
I'll chime in here with a couple comments. Regarding pytest, that sounds good for many of the tests. eigen.py/cpp (which you converted) comes to mind: it mostly consists of output of a bunch of lines such as "test_whatever() OK" or "test_whatever() FAILED"--which is basically already just a test script, but with less sophistication than pytest would give us. On the other hand, for many of the example scripts in example/*.py the python code is, conceptually, part of the example, where providing the python-side counterpart to something in pybind11 is useful. With the current example/whatever.{cpp,py} structure it's obvious where the associated python code is, but it's a little less transparent with the python test code. Perhaps just adding something like "see tests/whatever.py for python code testing this example" to the .cpp headers would address this? Regarding the constructor/destructor output, I think the current tracking-constructors-via-print statements is worthwhile getting rid of in favour of an object that tracks matching construction/destruction pairs. See PR #324 for code that does exactly this (and gets rid of the need for relaxed mode entirely). |
I forgot to mention this in the original post, but the example/tests split which exists in the current commit is mainly because of the WIP status. I expect that I'll need to rebase this a few times because other PRs will modify the Now the question is where to put it all back together. I can see either:
I'm in favor of the second since it seems to be a more accurate name. Some of the current examples do very detailed testing including implementation details which are not that interesting for someone just looking for examples. In general, a good example is not necessarily a good test, and vice versa. Perhaps it would be nice to have the Regarding constructor/destructor tracking, I'll comment in the other PR. |
This looks like a nice cleanup (especially if combined with the constructor/destructor tracking from the other PR). Note that I would prefer it if the tests and the .cpp code stay in the same directory (the code in there is matched 1:1, so it seems weird to me to separate them into different directories). |
The separate tests and .cpp files are only temporary to prevent merge conflicts during the development of this PR. See my last post. Do you have any preference for the final destination: put everything back into |
Got it -- renaming to |
OK, I've ported all the tests. This is probably difficult to review due to the size, so I made the changes in 2 stages:
|
The last two commits just organize things a little.
|
Updated the documentation. Rather than explain how to install pytest, I made CMake do it automatically. Note that the first commit fixes some pre-existing sphinx warnings: the code block in this section are not currently displayed. |
One of the AppVeyor builds timed out without even starting. Looks like an issue on their end and the build just needs to be restarted. |
I'd like to add, it may make sense to go over the tests and see where the output / the ref file is completely redundant (maybe in 90% of them it is). It's much better to do things like assert f() == 'foobar' (and in case you use pytest, it will use its assertions engine to give you meaningful diffs if something's off) than print(f()) # match in .ref file |
@@ -102,7 +102,7 @@ C++ side, or to perform other types of customization. | |||
|
|||
.. seealso:: | |||
|
|||
The file :file:`example/example-operator-overloading.cpp` contains a | |||
The file :file:`tests/test_operator-overloading.cpp` contains a |
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.
Minor, but wouldn't it be a bit cleaner to use one convention for test filenames, dashes or underscore? I.e., test_operator_overloading
.
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.
Yeah, I'll rename the files on the next rebase.
Thanks, looks pretty good, modulo a few minor comments. One other thought I had regarding the test suite, would it make sense to actually build multiple Python test modules and not just one big lump of stuff (i.e. |
Just to add to the previous comment re: splitting into multiple modules: aside from cosmetic reasons and avoiding name clashes, this would also be helpful during development when the code in module initialization can fail and the whole test suite then collapses with all tests throwing |
New commits address some of the comments -- I'll squash these with the next rebase if the changes look good. Regarding splitting up the tests, it would be possible to make separate submodules per test as is currently the case for |
Splitting into submodules seems like one way to go. Would it avoid import errors in all submodules if a single submodule fails in module initialization? |
def test_eval(capture): | ||
from pybind11_tests import example_eval | ||
|
||
with capture: |
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.
For this test, I think all testing logic should be moved to Python and capture/std::cout eradicated; instead of doing the actual testing, the cpp file could just expose a few functions like
.def("eval_single_statement",
[&](std::string s) { py::eval<py::eval_single_statement>(s, main_namespace); })
.def("eval_expr",
[&](std::string s) { return (py::object) py::eval(s, main_namespace); })
.def("eval_file",
[&](std::string s) { return (py::object) py::eval_file(s, main_namespace); })
so they can be called from Python, making use of pytest assertions etc.
Use simple asserts and pytest's powerful introspection to make testing simpler. This merges the old .py/.ref file pairs into simple .py files where the expected values are right next to the code being tested. This commit does not touch the C++ part of the code and replicates the Python tests exactly like the old .ref-file-based approach.
The C++ part of the test code is modified to achieve this. As a result, this kind of test: ```python with capture: kw_func1(5, y=10) assert capture == "kw_func(x=5, y=10)" ``` can be replaced with a simple: `assert kw_func1(5, y=10) == "x=5, y=10"`
There are more enum tests than 'constants and functions'.
Pytest is a development dependency but we can make it painless by automating the install using CMake.
Test compilation instructions for Windows were changed to use the `cmake --build` command line invocation which should be easier than manually setting up using the CMake GUI and Visual Studio.
Most of the test code is left in C++ since this is the intended use case for the eval functions.
Rebased onto master and ported new tests from #343 and 8de0437. Other changes:
There are still some multiline print/capture segments (e.g. in
Unfortunately, it looks like all binary submodules are initialized at the same time, so this wouldn't solve the import problem. Splitting into actual modules may be the only way to go, but I'm afraid this would lead to a lot of code duplication. I'll look into it. |
Just a quick comment: I think that working with one binary is fine for now. If a change causes the whole test suite to come down crashing then that is useful feedback as well (plus, instantiating the whole module rather than just a few tiny parts is a nice test of reference counting correctness for types and functions) |
Sure, makes sense. Simple submodules can be used just to avoid name clashes in future tests. I took a brief look at maybe splitting the existing tests into submodules, but it wouldn't really add any value at this point -- future tests can easily just add submodules as needed. |
This is fantastic 🎉 -- merged! |
Updated information
Thanks to PR #324 constructor/destructor testing has become easier, so this PR was modified as well.
Im short, this PR ports all test cases from
print output + reference file
checks to the pytest framework which usesassert
statements and a nice introspection engine for reporting failures.For tests which still require output capture, this PR extends pytest's builtin
capfd
output capture function for the specific needs of pybind11. It allows the following kind of test:In addition, the assert can also use
capture.unordered
which does not enforce line order.However, it is preferable to avoid output capture where possible. This PR converts most tests from:
to simple one-line asserts:
Original proposal (outdated info)
I realize this is not the best time for this proposal -- the multiple PRs currently in flight are already stepping on each other (causing merge conflicts) and this would be even more disruptive. Nevertheless, I've been tinkering with this idea. I'll leave this here for your consideration, but there's no hurry.
This PR proposes porting the test suite to pytest. This should make for easier development. The nice thing is that most of the tests can be done by directly comparing to values with simple asserts (instead of a reference file) and pytest's failure reports make it easy to debug and pinpoint problems. For the tests that still require output capturing (constructors/destructors), pytest offers more localized pre-line output capturing (more details below). This should make it much easier to navigate the tests since the
.py
and.ref
files are merged into one and the expected values/stdout are right next to the code.The commit contains a proof of concept: the testing framework is complete, but only some of the tests have been ported so far. For clarity, the existing
example
dir is untouched and the pytest stuff is in thetests
dir. Travis and AppVeyor are already configured to run pytest. To run it locally, justmake pytest
.conftest.py
is pytest's configuration file. I've extended the builtincapfd
output capture function for the specific needs of pybind11. For example:However, since constructors can't be compared reliably, there are 3 output comparison modes:
capture.output
will expect an exact match to the expected string.capture.relaxed
is intended to constructor/destructors and will be disabled on some compilers like the current--relaxed
flag.capture.unordered
does not enforce line order which is intended fordict
andset
random order output.There is also a custom
doc
fixture which retrieves and sanitizes docstrings.Downsides: This adds pytest as dev time dependency, but it should be easy enough to install with just
pip install pytest
.