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

Add pytest-based tests and remove nose tests. #721

Merged
merged 5 commits into from
Aug 7, 2024

Conversation

ketch
Copy link
Member

@ketch ketch commented May 7, 2024

Since nose is dead, this PR begins the switch to using pytest in PyClaw. Some notes:

  • I'm not using the intricate machinery from pyclaw.utils that helped to generate many variants of the same test. That code doesn't immediately work with pytest (which doesn't support yielding tests) and it seems in some ways preferable to have more straightforward (easy to understand) testing code, even if it means being a bit more verbose about the definition of the tests.
  • In addition to the regression tests (which check that the output hasn't changed from what it used to be), I've added some accuracy tests where we run on a reasonably fine grid and check against the exact solution.
  • This does not include any parallel tests. We need to add those, but that will be left to a future PR.

@ketch ketch requested review from mandli and rjleveque May 7, 2024 07:01
@mandli
Copy link
Member

mandli commented May 7, 2024

This works well for me.

I am onboard with the idea of making the tests more verbose and also adding accuracy tests where we can. What would be even better would be convergence but I imagine that being a bit tricky.

@rjleveque
Copy link
Member

I also agree it would be best to make the tests more verbose and easier to understand and modify.

I wonder about running convergence tests, since part of the point of the tests is to make them run very quickly so that we do it frequently, and so they can be done automatically for PRs. Testing convergence is of course important, but once it's been determined that a particular test behaves properly in this regard, isn't it enough to test a few things coming out of one quick run to make sure the example hasn't broken in some way due to changes elsewhere? I thought that was the point of these unit tests, not to validate the numerical method.

We should talk about how to make similar changes in other repos, e.g. updating clawpack/amrclaw#279.

A couple other thoughts regarding the Fortran code tests:

  • Rather than redirecting output to a scratch directory, I think it would be simpler if we just sent it to _output as usual. These tests all produce very minimal output, and when things go wrong it would be easier to find the output in the standard location.
  • In the old system, when tests pass, the output directory is deleted. If possible it would be nice to add an option so that the tests can be run with the output retained, which is often useful if trying to do a dual debug to figure what went wrong in a new version where the test does not pass. Sometimes one can just do make .output at the command line to make the output, but sometimes the tests set up some data that is used in the test and then it's not so easy to figure out how to run the test and view the output (or at least that's my memory, admittedly I haven't done this in a while.)

@ketch
Copy link
Member Author

ketch commented May 7, 2024

Thanks for the comments! In the meantime, I did figure out how to setup similar tests with different function arguments in a more streamlined way: https://docs.pytest.org/en/7.1.x/example/parametrize.html

But maybe it's better to just be verbose and explicit. We can discuss it later today.

* Add test for  nonunif advection and remove nose from advection

* Add tests for 1d advection with variable coeffs

Testing for some custom RK time integrators, relies on
dictionaries.

* rephrasing comment

* Add tests for 2D advection

Testing transverse solver.
I think there is too much numerical dissipation with such a coarse grid
to compare initial conditions with solution at final time.
Using a dictionary with expected solutions instead.

* Add test advection_annulus

* Update test for advection 1D with nonuniform grid

Using dictionary with expected solution instead of comparison between
initial condition and final solution.

* Remove unnecessary abs

* Update docstring

* Rename classes for consistency

* Add test Burgers 1d

* Fix bug relative path

* Update tests Euler 2D

Switch to pytest and use a single test file.
I am not using the tools from pyclaw.utils. Should we test them with
the examples? If so, maybe we should keep them in one or two examples.
I am skipping tests for shock_forward_step, so the HLLE RS with walls is
not tested. This example gives me some random NaNs that I am not able to
handle robustly.

* Add tests Euler 3D

Add tests for the other 2 examples for 3D Euler.

* Do not abort execution if there is no MPI

* Add init file for relative imports to work

Is there a reason to not treat this directory as a package?

* Better to not treat this one as package for the moment

* Update test Euler with gravity 3d

I didn't see why the test (with nose) was skipped
if scipy couldn't be found. I removed that requirement here.

The execution of both examples is not aborted now if mpi4py
is not available, just a warning is displayed.

The same .txt file with
data for the regression test is used.

The local Makefile is used to
compile the source with subprocess.

* Redirect output Sedov Euler 3d

* Skipping PeanoClaw tests for the moment

* Handle relative path for Pytest

* Fix import

* Disable output when testing

* Update test psystem 2d

Updated to work with pytest and removed dependence on pyclaw.util.

* Remove nosetest Euler w gravity 3D

* Add pytest SWEs 1D, remove nosetests

* Redirect output and remove nose calls

* Fix test class name

* Add pytests SWEs 2D

* Remove nose call

* Add pytests stegoton 1d

* Passing some paremeters as arguments for setup

* Add pytests traffic 1D

* Remove nose call

* Add expected solutions Burgers 1D

---------

Co-authored-by: Carlos Munoz Moncayo <munozmca@kl-26147.kaust.edu.sa>
Co-authored-by: Carlos Munoz Moncayo <munozmca@kl-26147.local>
@ketch ketch changed the title [WIP] Add pytest-based tests and remove nose tests. Add pytest-based tests and remove nose tests. Jun 29, 2024
@ketch
Copy link
Member Author

ketch commented Jun 29, 2024

I think this is ready to go, thanks mostly to @carlosmunozmoncayo . Any additional comments from @mandli @rjleveque are welcome.

@rjleveque
Copy link
Member

Thanks @ketch and @carlosmunozmoncayo , you've done a lot of work on this!

Many of the tests pass for me, but the one in examples/shallow_2d/radial_dam_break.py fails with:

E               AssertionError: Test radialdambreak_sharpclaw_hlle failed
E               assert 1.9122676246041693e-06 < 1e-06
E                +  where 1.9122676246041693e-06 = error(test_name='radialdambreak_sharpclaw_hlle', solver_type='sharpclaw', riemann_solver='hlle', disable_output=True)
test_shallow2d.py:32: AssertionError

and the one in shallow_sphere/test_shallow_sphere.py dies with a segmentation fault,

(claw510) [shallow_sphere] niwot $ pytest -v .
============================= test session starts ==============================
platform darwin -- Python 3.10.13, pytest-8.0.1, pluggy-1.4.0 -- /Users/rjl/venv/claw510/bin/python3.10
cachedir: .pytest_cache
rootdir: /Users/rjl/Downloads/clawpack-v5.10.0pyclaw/pyclaw/examples/shallow_sphere
collected 1 item                                                               

test_shallow_sphere.py::test_shallow_sphere Fatal Python error: Segmentation fault

Current thread 0x00000001052b8580 (most recent call first):
  File "/Users/rjl/Downloads/clawpack-v5.10.0pyclaw/pyclaw/examples/shallow_sphere/Rossby_wave.py", line 444 in setup
  File "/Users/rjl/Downloads/clawpack-v5.10.0pyclaw/pyclaw/src/pyclaw/util.py", line 235 in test_app
  File "/Users/rjl/Downloads/clawpack-v5.10.0pyclaw/pyclaw/examples/shallow_sphere/test_shallow_sphere.py", line 23 in test_shallow_sphere
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/python.py", line 194 in pytest_pyfunc_call
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_callers.py", line 102 in _multicall
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_manager.py", line 119 in _hookexec
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_hooks.py", line 501 in __call__
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/python.py", line 1831 in runtest
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 174 in pytest_runtest_call
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_callers.py", line 102 in _multicall
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_manager.py", line 119 in _hookexec
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_hooks.py", line 501 in __call__
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 267 in <lambda>
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 346 in from_call
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 266 in call_runtest_hook
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 227 in call_and_report
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 134 in runtestprotocol
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/runner.py", line 115 in pytest_runtest_protocol
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_callers.py", line 102 in _multicall
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_manager.py", line 119 in _hookexec
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_hooks.py", line 501 in __call__
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/main.py", line 352 in pytest_runtestloop
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_callers.py", line 102 in _multicall
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_manager.py", line 119 in _hookexec
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_hooks.py", line 501 in __call__
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/main.py", line 327 in _main
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/main.py", line 273 in wrap_session
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/main.py", line 320 in pytest_cmdline_main
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_callers.py", line 102 in _multicall
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_manager.py", line 119 in _hookexec
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/pluggy/_hooks.py", line 501 in __call__
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/config/__init__.py", line 175 in main
  File "/Users/rjl/venv/claw510/lib/python3.10/site-packages/_pytest/config/__init__.py", line 198 in console_main
  File "/Users/rjl/venv/claw510/bin/pytest", line 8 in <module>

Extension modules: numpy.core._multiarray_umath, numpy.core._multiarray_tests, numpy.linalg._umath_linalg, numpy.fft._pocketfft_internal, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._mt19937, numpy.random.mtrand, numpy.random._philox, numpy.random._pcg64, numpy.random._sfc64, numpy.random._generator, clawpack.riemann.acoustics_1D, clawpack.riemann.acoustics_variable_1D, clawpack.riemann.acoustics_1D_ptwise, clawpack.riemann.advection_1D, clawpack.riemann.advection_1D_ptwise, clawpack.riemann.advection_color_1D, clawpack.riemann.burgers_1D, clawpack.riemann.cubic_1D, clawpack.riemann.euler_with_efix_1D, clawpack.riemann.euler_hlle_1D, clawpack.riemann.mhd_roe_1D, clawpack.riemann.nonlinear_elasticity_fwave_1D, clawpack.riemann.psystem_fwave_1D, clawpack.riemann.reactive_euler_with_efix_1D, clawpack.riemann.shallow_hlle_1D, clawpack.riemann.shallow_roe_with_efix_1D, clawpack.riemann.shallow_bathymetry_fwave_1D, clawpack.riemann.shallow_roe_tracer_1D, clawpack.riemann.traffic_1D, clawpack.riemann.traffic_vc_1D, clawpack.riemann.traffic_vc_fwave_1D, clawpack.riemann.traffic_vc_tracer_1D, clawpack.riemann.acoustics_2D, clawpack.riemann.acoustics_mapped_2D, clawpack.riemann.acoustics_2D_ptwise, clawpack.riemann.advection_2D, clawpack.riemann.burgers_2D, clawpack.riemann.euler_mapgrid_2D, clawpack.riemann.euler_5wave_2D, clawpack.riemann.euler_4wave_2D, clawpack.riemann.euler_hlle_2D, clawpack.riemann.euler_hlle_with_walls_2D, clawpack.riemann.kpp_2D, clawpack.riemann.psystem_2D, clawpack.riemann.shallow_hlle_2D, clawpack.riemann.shallow_roe_with_efix_2D, clawpack.riemann.shallow_bathymetry_fwave_2D, clawpack.riemann.sw_aug_2D, clawpack.riemann.shallow_sphere_2D, clawpack.riemann.vc_acoustics_2D, clawpack.riemann.vc_advection_2D, clawpack.riemann.vc_elasticity_2D, clawpack.riemann.vc_acoustics_3D, clawpack.riemann.euler_3D, clawpack.riemann.burgers_3D, clawpack.riemann.vc_advection_3D, matplotlib._c_internal_utils, PIL._imaging, matplotlib._path, kiwisolver._cext, matplotlib._image, clawpack.pyclaw.examples.shallow_sphere.sw_sphere_problem, clawpack.pyclaw.classic.classic2_sw_sphere (total: 66)
Segmentation fault: 11

Copy link
Member

@mandli mandli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running pytest from the root catches the tests in development. Instead I ran pytest src examples.

Output:

➜  pyclaw git:(add_pytests) ✗ pytest examples src
============================= test session starts ==============================
platform darwin -- Python 3.11.9, pytest-7.4.3, pluggy-1.3.0
rootdir: /Users/kmandli/Documents/src/clawpack
plugins: anyio-4.0.0
collected 67 items

examples/acoustics_1d_homogeneous/test_acoustics.py ......               [  8%]
examples/acoustics_2d_homogeneous/test_2d_acoustics.py ....              [ 14%]
examples/acoustics_2d_mapped/test_acoustics_2d_mapped.py .               [ 16%]
examples/acoustics_2d_variable/test_acoustics_2d_variable.py ...         [ 20%]
examples/acoustics_2d_variable/test_acoustics_2d_variable_io.py .        [ 22%]
examples/acoustics_3d_variable/test_3d_acoustics.py ..                   [ 25%]
examples/advection_1d/test_advection.py ......                           [ 34%]
examples/advection_1d/test_advection_nonunif.py ..                       [ 37%]
examples/advection_1d_variable/test_variable_coefficient_advection.py .. [ 40%]
...                                                                      [ 44%]
examples/advection_2d/test_2d_advection.py .....                         [ 52%]
examples/advection_2d_annulus/test_2d_advection_annulus.py .             [ 53%]
examples/advection_reaction_2d/test_advection_reaction.py .              [ 55%]
examples/burgers_1d/test_burgers_1d.py ....                              [ 61%]
examples/cubic_1d/test_cubic.py .                                        [ 62%]
examples/euler_1d/test_shocksine.py .                                    [ 64%]
examples/euler_1d/test_shocktube.py .                                    [ 65%]
examples/euler_1d/test_woodward_colella_blast.py .                       [ 67%]
examples/euler_2d/test_euler2d.py ...                                    [ 71%]
examples/euler_3d/test_euler3d.py ..s                                    [ 76%]
examples/euler_gravity_3d/test_eulerg3d.py F                             [ 77%]
examples/mhd_1d/test_shocktube.py .                                      [ 79%]
examples/peano_shallow_2d/test_identical_grids.py s                      [ 80%]
examples/peano_shallow_2d/test_peano_solver.py s                         [ 82%]
examples/psystem_2d/test_2d_psystem.py .                                 [ 83%]
examples/shallow_1d/test_shallow1d.py ..                                 [ 86%]
examples/shallow_2d/test_shallow2d.py ..                                 [ 89%]
examples/shallow_sphere/test_shallow_sphere.py .                         [ 91%]
examples/stegoton_1d/test_stego.py ....                                  [ 97%]
examples/traffic/test_traffic.py ..                                      [100%]

The failure in test_eulerg3d.py looks to be due to a missing import for mappedGrid coming from rising_hot_sphere.py, which does have the line

from mappedGrid import euler3d_mappedgrid as mg

The problem here of course is that mappedGrid is compiled. Once I did that it worked. Was this supposed to be built at installation? The broader point I am bringing up though is that without guessing that I needed to go in and build the module I am not sure how to have known to do this. Not sure that the test should try and do this but some information might be useful. This will also be important for CI testing.

Also, while I was looking at the Makefile in there I noticed that the path to the riemann repository was relative rather than using CLAW. Was there a reason to do this? Although certainly an uncommon occurrence It seems unsafe to assume relative paths between the sub-repositories.

@carlosmunozmoncayo
Copy link
Contributor

Thank you very much for your feedback!

@rjleveque the test examples/shallow_2d/radial_dam_break.py also failed for me on a different machine. I realized that the previous tolerance was larger. I will set it back to the original value.
I have not been able to reproduce the error with examples/shallow_sphere/test_shallow_sphere.py. In this PR, only a call to nose was removed from that test. Could the segmentation fault error be related to an issue with the compilation of sw_sphere_problem or classic2_sw_sphere at installation time?

@mandli mappedGrid and euler_3d_gmap are not built at installation. test_eulerg3d.py uses a subprocess to compile them before importing rising_hot_sphere. However, the subprocess uses the Makefile which, as you mention, relies on a relative path to the riemann repository to look for the sources. Since these modules are only called from the files in pyclaw/examples/euler_gravity_3d, we could move the source there and compile at installation, similarly to what is done for examples/shallow_sphere and examples/advection_reaction_2d.

@ketch
Copy link
Member Author

ketch commented Jul 3, 2024

Yes, it would be good to make the 3D euler example be compiled and installed at run-time. @carlosmunozmoncayo if you want to do that, that would be great. I suppose it should be a separate pull request that could be merged before this one.

@mandli We don't require CLAW to be set for PyClaw installation or usage.

@mandli
Copy link
Member

mandli commented Jul 3, 2024

mappedGrid and euler_3d_gmap are not built at installation. test_eulerg3d.py uses a subprocess to compile them before importing rising_hot_sphere. However, the subprocess uses the Makefile which, as you mention, relies on a relative path to the riemann repository to look for the sources. Since these modules are only called from the files in pyclaw/examples/euler_gravity_3d, we could move the source there and compile at installation, similarly to what is done for examples/shallow_sphere and examples/advection_reaction_2d.

I saw that, not suer why it did not compile for me. May be unrelated.

We don't require CLAW to be set for PyClaw installation or usage.

I did vaguely recall that but I am wondering if this is a good idea. I had not really thought about this but the placement of other repositories relative to PyClaw seems like it could break. We could set the variable by default to be relative so the user would not need to do this usually but then one could stick Riemann or something else anywhere if they were so inclined. Not sure though as such as user would also more than likely be savvy enough to see the path and know to change it. 🤷

@ketch
Copy link
Member Author

ketch commented Jul 4, 2024

@mandli I agree it's worth thinking about. From my perspective, adding a requirement to set an environment variable is a significant additional barrier to some users. So far I've never received a report from someone having an issue with this relative path. It seems like a very rare corner case since they would have to move repositories around and try to run this specific example and not know enough to look at the path in the makefile and correct it.

@mandli
Copy link
Member

mandli commented Jul 5, 2024

@ketch Any change should not cause any additional requirements for sure. I was thinking that something like a make variable that can be over ridden if not defined to do the relative path would work but it's not that big of a deal.

@ketch
Copy link
Member Author

ketch commented Jul 5, 2024

@mandli Oh, you're right; I hadn't understood what you meant. Defaulting to using CLAW if it is set is a good idea.

@ketch
Copy link
Member Author

ketch commented Aug 7, 2024

@rjleveque @mandli I've merged some other PRs with changes relative to your comments. I'd like to also go ahead and merge this, given how much it improves the state of pyclaw testing, even if some issues may remain. Then it would be great if you could run the tests again and open issues for any failures.

@mandli
Copy link
Member

mandli commented Aug 7, 2024

Agreed.

@mandli mandli merged commit 1d7ba17 into clawpack:master Aug 7, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants