-
Notifications
You must be signed in to change notification settings - Fork 37
solver parameterised pytest #780
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
Draft
ThomSerg
wants to merge
58
commits into
master
Choose a base branch
from
pytest_parametrised
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
0b74938
solver parameterised pytest
ThomSerg 136754c
Standardise skipping on solver dependency
ThomSerg 531d362
Support multiple solvers, all and None
ThomSerg dca989f
Add tests README
ThomSerg 175486c
Skip if pypblib installed
ThomSerg 1e90ad5
Explicit check no solver arg
ThomSerg d98a8b8
Add logging
ThomSerg 13c2259
Remove | in type hints
ThomSerg 6122785
Merge branch 'master' into pytest_parametrised
ThomSerg 3751230
Skip example test when failing due to missing dependency
ThomSerg df6c3d4
Also skip non-advanced examples on missing dep
ThomSerg 991efbf
Merge branch 'master' into pytest_parametrised
ThomSerg 5b0c0c1
Small improvements
ThomSerg f1b5c7a
Update newly added tests
ThomSerg 0d2af6c
skip example that wants to open visualisation in browser
ThomSerg 6447728
unparametrise tests that take too long
ThomSerg 11b16b3
remove old imports in test_direct
IgnaceBleukx 7f347b5
fix test names and fix mzn test
IgnaceBleukx fd5c87c
import pypblib in supported
IgnaceBleukx c9a72b2
always use ortools in test
IgnaceBleukx 0d53543
yield expression instead of constructing first
IgnaceBleukx 9d71d7b
Add generate_constraints marker
ThomSerg a2bf757
Log solver option processing
ThomSerg 9c4b00d
Add '-n auto' to docs
ThomSerg d66bac3
Docs example skip solvers
ThomSerg 91bbd7d
raise e
ThomSerg f65a257
Add to docs
ThomSerg 4cc5d28
Forgot use of markers
ThomSerg 4dc0d36
Add to docs
ThomSerg 7a557b3
Fix run without solver arg
ThomSerg 8c51924
Add comment
ThomSerg 25cc052
Merge branch 'master' into pytest_parametrised
ThomSerg b445ca5
New and missing parametrisations
ThomSerg 27b684b
Add global func generator for pow, fiv, modl
ThomSerg a4a2a17
Ruff testsuite cleanup
ThomSerg c96d95c
Fix solution hinting test
ThomSerg 22ba1f7
Fix cp.any
ThomSerg 3925c2a
reuse test argument generator marker
ThomSerg 3f8a9cf
Update test_constraints to yield LexChainLessEq instead of LexChainLess
IgnaceBleukx bd99a61
Update test_expressions to use smaller array shapes for faster tests
IgnaceBleukx 16244cb
Update test_globalconstraints to use smaller variable ranges for glob…
IgnaceBleukx 7a2b7ed
Tuner test requires ortools
ThomSerg e803dac
simplify conftest
ThomSerg 2b6e6fe
make more use of markers
ThomSerg 5a22bb8
remove unused code
ThomSerg 3398b30
update pytest readme
ThomSerg f6bb7c7
only use basesolvers
ThomSerg 71f2727
only installed solvers for all
ThomSerg 8c1e3f5
put pytest components together
ThomSerg 3881cc8
Merge remote-tracking branch 'origin/master' into pytest_parametrised
ThomSerg a07aed3
marker support for required solver as dependency
ThomSerg ddfd84d
pytest parametrisation compatible TestCase
ThomSerg d672669
Fix test class filtering
ThomSerg c7c818f
rename helper function
ThomSerg 23fc2d9
update docs
ThomSerg 44098e0
small test for github runner
ThomSerg ebb2948
Remove pysdd from all
ThomSerg 146a79f
Un-parametrise examples
ThomSerg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ```{include} ../tests/README.md | ||
| ``` | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| # Test Suite | ||
|
|
||
| CPMpy has an extensive test suite, covering all major components including variables, constraints, models, solvers, transformations, and tools. | ||
|
|
||
| ## Running Tests | ||
|
|
||
| ### Basic Usage | ||
|
|
||
| Run all tests: | ||
| ```bash | ||
| pytest | ||
| ``` | ||
|
|
||
| Run a specific test file: | ||
| ```bash | ||
| pytest tests/test_model.py | ||
| ``` | ||
|
|
||
| Run a specific test: | ||
| ```bash | ||
| pytest tests/test_model.py::TestModel::test_ndarray | ||
| ``` | ||
|
|
||
| ### Parallelisation | ||
|
|
||
| Through the `pytest-xdist` pytest plugin, running tests can be parallelised. | ||
| E.g. running with 40 workers: | ||
| ```console | ||
| pytest -n 40 tests/test_model.py | ||
| ``` | ||
|
|
||
| Or letting pytest decide how many workers to use: | ||
| ```console | ||
| pytest -n auto tests/test_model.py | ||
| ``` | ||
|
|
||
| Install using: | ||
| ```console | ||
| pip install pytest-xdist | ||
| ``` | ||
|
|
||
| ### Solver Selection | ||
|
|
||
| The test suite supports changing the solver backend used to run the tests via the `--solver` command-line option: | ||
|
|
||
| #### Single Solver | ||
| Run tests with a specific solver: | ||
| ```bash | ||
| pytest --solver=gurobi | ||
| ``` | ||
|
|
||
| #### Multiple Solvers | ||
| Run tests with multiple solvers | ||
| - non-solver-specific tests will run against all specified solvers | ||
| - solver-specific tests will be filtered on specified solvers | ||
| ```bash | ||
| pytest --solver=ortools,cplex,gurobi | ||
| ``` | ||
|
|
||
| #### All Installed Solvers | ||
| Run tests with all installed solvers: | ||
| ```bash | ||
| pytest --solver=all | ||
| ``` | ||
|
|
||
| This automatically detects all installed solvers from `SolverLookup` and parametrises non-solver-specific tests to run against each one. | ||
|
|
||
| #### Skip Solver Tests | ||
| Skip all solver-parametrised tests (only run tests that don't depend on solver parametrisation). | ||
| I.e., tests that do not rely on solving a model. Examples are tests that evaluate constructors of expressions. | ||
| ```bash | ||
| pytest --solver=None | ||
| ``` | ||
|
|
||
| #### Default Behavior | ||
|
|
||
| If no `--solver` option is provided: | ||
| - Non-solver-specific tests run with the default solver (OR-Tools) | ||
| - All solver-specific tests run for their respective declared solver (if installed) | ||
|
|
||
| ## Test Organization | ||
|
|
||
| ### Test Files | ||
|
|
||
| - **`test_model.py`** - Model creation, manipulation, and I/O | ||
| - **`test_expressions.py`** - Expression types and operations (comparisons, operators, sums, etc.) | ||
| - **`test_constraints.py`** - Constraint types and validation (boolean, comparison, reification, implication) | ||
| - **`test_globalconstraints.py`** - Global constraint implementations (AllDifferent, Circuit, Cumulative, etc.) | ||
| - **`test_solvers.py`** - Solver interface and functionality (high-level solver tests) | ||
| - **`test_solverinterface.py`** - Low-level solver interface tests (constructor, native model, solve methods) | ||
| - **`test_variables.py`** - Variable types (intvar, boolvar, shapes, naming) | ||
| - **`test_builtins.py`** - Python builtin functions (max, min, all, any) | ||
| - **`test_cse.py`** - Common subexpression elimination | ||
| - **`test_direct.py`** - Direct solver constraints (automaton, etc.) | ||
| - **`test_flatten.py`** - Model flattening transformations | ||
| - **`test_int2bool.py`** - Integer to boolean transformation | ||
| - **`test_pysat_*.py`** - PySAT-specific tests (cardinality, interrupt, weighted sum) | ||
| - **`test_solveAll.py`** - solveAll functionality across solvers | ||
| - **`test_solvers_solhint.py`** - Solver hints functionality | ||
| - **`test_tocnf.py`** - Conversion to CNF (Conjunctive Normal Form) | ||
| - **`test_tool_dimacs.py`** - DIMACS format tools | ||
| - **`test_trans_*.py`** - Transformation tests (linearize, safen, simplify) | ||
| - **`test_transf_*.py`** - Additional transformation tests (comp, decompose, reif) | ||
| - **`test_tools_*.py`** - Tool functionality (MUS, tuning, etc.) | ||
| - **`test_examples.py`** - Run examples as a testsuite | ||
|
|
||
| ### Test Markers | ||
|
|
||
| Tests can be marked with special markers: | ||
|
|
||
| - **`@pytest.mark.requires_solver("solver_name_1", "solver_name_2", ...)`** - Test requires a specific solver, one of the listed names | ||
| - **`@pytest.mark.requires_dependency("package_name")`** - Test requires a specific Python package | ||
| - **`@pytest.mark.generate_constraints.with_args(generator_function)`** - Parametrise test's "constraint" argument using the provided generator | ||
|
|
||
|
|
||
| Examples: | ||
| ```python | ||
| @pytest.mark.requires_solver("cplex") | ||
| def test_cplex_specific_feature(): | ||
| # This test only runs if cplex is available | ||
| pass | ||
| ``` | ||
|
|
||
| ```python | ||
| def randomly_sample_expressions(solver) | ||
| return [...] | ||
|
|
||
| @pytest.mark.generate_constraints.with_args(randomly_sample_expressions) | ||
| def test_bool_constraints(solver, constraint): | ||
| ... | ||
| ``` | ||
| (for a complete example, have a look at `/tests/test_constraints.py`) | ||
|
|
||
| ## Writing Tests | ||
|
|
||
| ### Basic Test Structure | ||
|
|
||
| ```python | ||
| import pytest | ||
| import cpmpy as cp | ||
|
|
||
| def test_basic_model(): | ||
| x = cp.intvar(0, 10, name="x") | ||
| m = cp.Model(x >= 5) | ||
| assert m.solve() | ||
| assert x.value() >= 5 | ||
| ``` | ||
|
|
||
| ### Using the Custom TestCase Class | ||
|
|
||
| CPMpy's test suite includes a custom `TestCase` class (in `tests/utils.py`) that provides all unittest-style assertion methods without inheriting from `unittest.TestCase`. This design allows pytest's `pytest_generate_tests` parametrization to work properly. | ||
|
|
||
| #### Benefits | ||
|
|
||
| - Access to all familiar unittest assertions: `assertEqual`, `assertTrue`, `assertIn`, `assertIsInstance`, etc. | ||
| - Compatible with pytest's parametrization and fixture system | ||
| - Automatic initialization of required unittest internal attributes | ||
| - Support for `setup_method` and `teardown_method` hooks | ||
|
|
||
| #### Usage | ||
|
|
||
| ```python | ||
| from utils import TestCase | ||
| import cpmpy as cp | ||
|
|
||
| class TestMyFeature(TestCase): | ||
| def setup_method(self): | ||
| # Called before each test method | ||
| self.x = cp.intvar(0, 10, name="x") | ||
|
|
||
| def test_example(self): | ||
| # Use unittest-style assertions | ||
| self.assertEqual(str(self.x), "x") | ||
| self.assertIsInstance(self.x, cp.intvar) | ||
| self.assertTrue(self.x.lb == 0) | ||
| self.assertIn(self.x, [self.x]) | ||
| ``` | ||
|
|
||
| ### Using the Solver Fixture | ||
|
|
||
| For tests that should run with different solvers: | ||
|
|
||
| ```python | ||
| from utils import TestCase | ||
|
|
||
| @pytest.mark.usefixtures("solver") | ||
| class TestMyFeature(TestCase): | ||
| def test_with_solver(self): | ||
| x = cp.intvar(0, 10) | ||
| m = cp.Model(x >= 5) | ||
| self.assertTrue(m.solve(solver=self.solver)) | ||
| self.assertGreaterEqual(x.value(), 5) | ||
| ``` | ||
|
|
||
| When multiple solvers are provided via `--solver`, these tests will automatically be parametrised to run against each solver. The `self.solver` attribute is automatically set by the test framework. | ||
|
|
||
| ### Solver-Parametrised Tests | ||
|
|
||
| For tests that are explicitly parametrised with a selection of solvers: | ||
|
|
||
| ```python | ||
| @pytest.mark.parametrise("solver", ["ortools", "cplex", "gurobi"]) | ||
| def test_with_explicit_solvers(solver): | ||
| x = cp.intvar(0, 10) | ||
| m = cp.Model(x >= 5) | ||
| assert m.solve(solver=solver) | ||
| ``` | ||
|
|
||
| ### Solver-Specific Tests | ||
|
|
||
| For tests that only work with specific solvers: | ||
|
|
||
| ```python | ||
| @pytest.mark.requires_solver("cplex") | ||
| def test_cplex_feature(): | ||
| # Test cplex-specific functionality | ||
| pass | ||
| ``` | ||
|
|
||
| ## Contributing | ||
|
|
||
| When adding new tests: | ||
|
|
||
| 1. Follow existing test patterns | ||
| 2. Use appropriate markers for solver-specific tests | ||
| 3. Ensure tests work with multiple solvers when possible | ||
| 4. Add docstrings explaining what the test validates | ||
| 5. Use descriptive test names | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.