Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0b74938
solver parameterised pytest
ThomSerg Oct 24, 2025
136754c
Standardise skipping on solver dependency
ThomSerg Nov 6, 2025
531d362
Support multiple solvers, all and None
ThomSerg Dec 8, 2025
dca989f
Add tests README
ThomSerg Dec 8, 2025
175486c
Skip if pypblib installed
ThomSerg Dec 8, 2025
1e90ad5
Explicit check no solver arg
ThomSerg Dec 8, 2025
d98a8b8
Add logging
ThomSerg Dec 8, 2025
13c2259
Remove | in type hints
ThomSerg Dec 11, 2025
6122785
Merge branch 'master' into pytest_parametrised
ThomSerg Dec 11, 2025
3751230
Skip example test when failing due to missing dependency
ThomSerg Dec 11, 2025
df6c3d4
Also skip non-advanced examples on missing dep
ThomSerg Dec 18, 2025
991efbf
Merge branch 'master' into pytest_parametrised
ThomSerg Dec 18, 2025
5b0c0c1
Small improvements
ThomSerg Dec 18, 2025
f1b5c7a
Update newly added tests
ThomSerg Dec 18, 2025
0d2af6c
skip example that wants to open visualisation in browser
ThomSerg Dec 18, 2025
6447728
unparametrise tests that take too long
ThomSerg Dec 18, 2025
11b16b3
remove old imports in test_direct
IgnaceBleukx Dec 23, 2025
7f347b5
fix test names and fix mzn test
IgnaceBleukx Dec 23, 2025
fd5c87c
import pypblib in supported
IgnaceBleukx Dec 23, 2025
c9a72b2
always use ortools in test
IgnaceBleukx Dec 23, 2025
0d53543
yield expression instead of constructing first
IgnaceBleukx Dec 23, 2025
9d71d7b
Add generate_constraints marker
ThomSerg Dec 23, 2025
a2bf757
Log solver option processing
ThomSerg Dec 23, 2025
9c4b00d
Add '-n auto' to docs
ThomSerg Dec 23, 2025
d66bac3
Docs example skip solvers
ThomSerg Dec 23, 2025
91bbd7d
raise e
ThomSerg Dec 23, 2025
f65a257
Add to docs
ThomSerg Dec 23, 2025
4cc5d28
Forgot use of markers
ThomSerg Dec 23, 2025
4dc0d36
Add to docs
ThomSerg Dec 23, 2025
7a557b3
Fix run without solver arg
ThomSerg Dec 23, 2025
8c51924
Add comment
ThomSerg Dec 23, 2025
25cc052
Merge branch 'master' into pytest_parametrised
ThomSerg Jan 7, 2026
b445ca5
New and missing parametrisations
ThomSerg Jan 7, 2026
27b684b
Add global func generator for pow, fiv, modl
ThomSerg Jan 7, 2026
a4a2a17
Ruff testsuite cleanup
ThomSerg Jan 7, 2026
c96d95c
Fix solution hinting test
ThomSerg Jan 7, 2026
22ba1f7
Fix cp.any
ThomSerg Jan 7, 2026
3925c2a
reuse test argument generator marker
ThomSerg Jan 7, 2026
3f8a9cf
Update test_constraints to yield LexChainLessEq instead of LexChainLess
IgnaceBleukx Jan 7, 2026
bd99a61
Update test_expressions to use smaller array shapes for faster tests
IgnaceBleukx Jan 8, 2026
16244cb
Update test_globalconstraints to use smaller variable ranges for glob…
IgnaceBleukx Jan 8, 2026
7a2b7ed
Tuner test requires ortools
ThomSerg Jan 8, 2026
e803dac
simplify conftest
ThomSerg Jan 8, 2026
2b6e6fe
make more use of markers
ThomSerg Jan 8, 2026
5a22bb8
remove unused code
ThomSerg Jan 8, 2026
3398b30
update pytest readme
ThomSerg Jan 8, 2026
f6bb7c7
only use basesolvers
ThomSerg Jan 8, 2026
71f2727
only installed solvers for all
ThomSerg Jan 8, 2026
8c1e3f5
put pytest components together
ThomSerg Jan 8, 2026
3881cc8
Merge remote-tracking branch 'origin/master' into pytest_parametrised
ThomSerg Jan 12, 2026
a07aed3
marker support for required solver as dependency
ThomSerg Jan 12, 2026
ddfd84d
pytest parametrisation compatible TestCase
ThomSerg Jan 12, 2026
d672669
Fix test class filtering
ThomSerg Jan 12, 2026
c7c818f
rename helper function
ThomSerg Jan 12, 2026
23fc2d9
update docs
ThomSerg Jan 12, 2026
44098e0
small test for github runner
ThomSerg Jan 13, 2026
ebb2948
Remove pysdd from all
ThomSerg Jan 13, 2026
146a79f
Un-parametrise examples
ThomSerg Jan 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:

- name: Test with pytest
run: |
python -m pytest -n auto tests/
python -m pytest -n auto tests/ --solver all -ref
1 change: 1 addition & 0 deletions cpmpy/solvers/pysat.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def supported():
# try to import pypblib and avoid ever re-import by setting `_pb`
if not hasattr(CPM_pysat, ("_pb")):
try:
import pypblib # first do import ourself, to ensure we have the right exception if not present
from pysat import pb # require pypblib
"""The `pysat.pb` module if its dependency `pypblib` installed, `None` if we have not checked it yet, or `False` if we checked and it is *not* installed"""
CPM_pysat._pb = pb
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Different solvers excel at different problems. `Try multiple! <modeling.html#sel
unsat_core_extraction
developers
adding_solver
testing

Open Source
-----------
Expand Down
3 changes: 3 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```{include} ../tests/README.md
```

228 changes: 228 additions & 0 deletions tests/README.md
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
Comment thread
IgnaceBleukx marked this conversation as resolved.
```

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
Loading
Loading