diff --git a/docs/source/contributing/dev-workflow.md b/docs/source/contributing/dev-workflow.md index fe8b70e7703385d45f7fd2d53822424b193c2592..8daac8cbd179a9922d9d70ebbcee2cd7b5dbbba2 100644 --- a/docs/source/contributing/dev-workflow.md +++ b/docs/source/contributing/dev-workflow.md @@ -127,18 +127,16 @@ If you think a new module is ready to be type-checked, add an exception clause t ## Running the Test Suite Pystencils comes with an extensive and steadily growing suite of unit tests. -To run the testsuite, you may invoke a variant of the Nox `testsuite` session. -There are multiple different versions of the `testsuite` session, depending on whether you are testing with our -without CUDA, or which version of Python you wish to test with. -You can list the available sessions using `nox -l`. -Select one of the `testsuite` variants and run it via `nox -s "testsuite(<variant>)"`, e.g. -``` -nox -s "testsuite(cpu)" +To run the full testsuite, invoke the Nox `testsuite` session: + +```bash +nox -s testsuite ``` -for the CPU-only suite. -During the testsuite run, coverage information is collected and displayed using [coverage.py](https://coverage.readthedocs.io/en/7.6.10/). -You can display a detailed overview of code coverage by opening the generated `htmlcov/index.html` page. +:::{seealso} +[](#testing_pystencils) +::: + ## Building the Documentation diff --git a/docs/source/contributing/index.md b/docs/source/contributing/index.md index 04ad821ce5eacdc2f8712ca1f666652b078b7c0f..56c97509cbc4c0e3f312fade9fd08af90c4c9c3d 100644 --- a/docs/source/contributing/index.md +++ b/docs/source/contributing/index.md @@ -7,6 +7,7 @@ Pystencils is an open-source package licensed under the [AGPL v3](https://www.gn As such, the act of contributing to pystencils by submitting a merge request is taken as agreement to the terms of the licence. :::{toctree} -:maxdepth: 2 +:maxdepth: 2 dev-workflow +testing ::: diff --git a/docs/source/contributing/testing.md b/docs/source/contributing/testing.md new file mode 100644 index 0000000000000000000000000000000000000000..da8ca2d163e6af36268bbaf8f4ace5e9da850ab9 --- /dev/null +++ b/docs/source/contributing/testing.md @@ -0,0 +1,77 @@ +(testing_pystencils)= +# Testing pystencils + +The pystencils testsuite is located at the `tests` directory, +constructed using [pytest](https://pytest.org), +and automated through [Nox](https://nox.thea.codes). +On this page, you will find instructions on how to execute and extend it. + +## Running the Testsuite + +The fastest way to execute the pystencils test suite is through the `testsuite` Nox session: + +```bash +nox -s testsuite +``` + +There exist several configurations of the testsuite session, from which the above command will +select and execute only those that are available on your machine. + - *Python Versions:* The testsuite session can be run against all major Python versions between 3.10 and 3.13 (inclusive). + To only use a specific Python version, add the `-p 3.XX` argument to your Nox invocation; e.g. `nox -s testsuite -p 3.11`. + - *CuPy:* There exist three variants of `testsuite`, including or excluding tests for the CUDA GPU target: `cpu`, `cupy12` and `cupy13`. + To select one, append `(<variant>)` to the session name; e.g. `nox -s "testsuite(cupy12)"`. + +You may also pass options through to pytest via positional arguments after a pair of dashes, e.g.: + +```bash +nox -s testsuite -- -k "kernelcreation" +``` + +During the testsuite run, coverage information is collected using [coverage.py](https://coverage.readthedocs.io/en/7.6.10/), +and the results are exported to HTML. +You can display a detailed overview of code coverage by opening the generated `htmlcov/index.html` page. + +## Extending the Test Suite + +### Codegen Configurations via Fixtures + +In the pystencils test suite, it is often necessary to test code generation features against multiple targets. +To simplify this process, we provide a number of [pytest fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html) +you can and should use in your tests: + + - `target`: Provides code generation targets for your test. + Using this fixture will make pytest create a copy of your test for each target + available on the current machine (see {any}`Target.available_targets`). + - `gen_config`: Provides default code generation configurations for your test. + This fixture depends on `target` and provides a {any}`CreateKernelConfig` instance + with target-specific optimization options (in particular vectorization) enabled. + - `xp`: The `xp` fixture gives you either the *NumPy* (`np`) or the *CuPy* (`cp`) module, + depending on whether `target` is a CPU or GPU target. + +These fixtures are defined in `tests/fixtures.py`. + +### Overriding Fixtures + +Pytest allows you to locally override fixtures, which can be especially practical when you wish +to restrict the target selection of a test. +For example, the following test overrides `target` using a parametrization mark, +and uses this in combination with the `gen_config` fixture, which now +receives the overridden `target` parameter as input: + +```Python +@pytest.mark.parametrize("target", [Target.X86_SSE, Target.X86_AVX]) +def test_bogus(gen_config): + assert gen_config.target.is_vector_cpu() +``` + +## Testing with the Experimental CPU JIT + +Currently, the testsuite by default still uses the {any}`legacy CPU JIT compiler <LegacyCpuJit>`, +since the new CPU JIT compiler is still in an experimental stage. +To test your code against the new JIT compiler, pass the `--experimental-cpu-jit` option to pytest: + +```bash +nox -s testsuite -- --experimental-cpu-jit +``` + +This will alter the `gen_config` fixture, activating the experimental CPU JIT for CPU targets. diff --git a/noxfile.py b/noxfile.py index 11b3731ec8100cc4fb6c20d1e45ad6f115d35ffa..f5b20da4f4c13004bc5f640371d7fb5908e1850d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -86,9 +86,15 @@ def typecheck(session: nox.Session): session.run("mypy", "src/pystencils") -@nox.session(python=["3.10", "3.12", "3.13"], tags=["test"]) +@nox.session(python=["3.10", "3.11", "3.12", "3.13"], tags=["test"]) @nox.parametrize("cupy_version", [None, "12", "13"], ids=["cpu", "cupy12", "cupy13"]) def testsuite(session: nox.Session, cupy_version: str | None): + """Run the pystencils test suite. + + **Positional Arguments:** Any positional arguments passed to nox after `--` + are propagated to pytest. + """ + if cupy_version is not None: install_cupy(session, cupy_version, skip_if_no_cuda=True) @@ -108,6 +114,7 @@ def testsuite(session: nox.Session, cupy_version: str | None): "--html", "test-report/index.html", "--junitxml=report.xml", + *session.posargs ) session.run("coverage", "html") session.run("coverage", "xml") diff --git a/src/pystencils/jit/cpu/cpujit_pybind11.py b/src/pystencils/jit/cpu/cpujit_pybind11.py index a58e5bf03cfb753bf5d7c1836527f2b573b2ef22..90224b22b7405f7b48d4897071ee32b8fc7684c1 100644 --- a/src/pystencils/jit/cpu/cpujit_pybind11.py +++ b/src/pystencils/jit/cpu/cpujit_pybind11.py @@ -51,7 +51,7 @@ class Pybind11KernelModuleBuilder(ExtensionModuleBuilderBase): kernel_def = self._get_kernel_definition(kernel) kernel_args = [param.name for param in kernel.parameters] - includes = [f"#include {h}" for h in kernel.required_headers] + includes = [f"#include {h}" for h in sorted(kernel.required_headers)] from string import Template @@ -76,7 +76,7 @@ class Pybind11KernelModuleBuilder(ExtensionModuleBuilderBase): def _get_kernel_definition(self, kernel: Kernel) -> str: from ...backend.emission import CAstPrinter - printer = CAstPrinter(func_prefix="inline") + printer = CAstPrinter() return printer(kernel) diff --git a/src/pystencils/jit/cpu_extension_module.py b/src/pystencils/jit/cpu_extension_module.py index 55f1961ca5c00963c16912ada738788688a93452..fca043db90725a441cb8b0ed9b99765c53eecb8d 100644 --- a/src/pystencils/jit/cpu_extension_module.py +++ b/src/pystencils/jit/cpu_extension_module.py @@ -91,12 +91,14 @@ class PsKernelExtensioNModule: code += "\n" # Kernels and call wrappers + from ..backend.emission import CAstPrinter + printer = CAstPrinter(func_prefix="FUNC_PREFIX") for name, kernel in self._kernels.items(): old_name = kernel.name kernel.name = f"kernel_{name}" - code += kernel.get_c_code() + code += printer(kernel) code += "\n" code += emit_call_wrapper(name, kernel) code += "\n"