Thank you for considering a contribution!
- Python >= 3.10
- uv -- for dependency management and running tests
- just -- only needed if you want to run integration tests locally
(
uv tool install rust-just)
git clone https://github.com/linkml/linkml-project-copier.git
cd linkml-project-copier
uv sync --group testThe test suite has two tiers:
# Structural tests only (fast, ~25 seconds, no just/linkml needed)
uv run pytest -m "not integration" -v
# Integration tests only (slow, minutes, needs just + network)
uv run pytest -m integration -v
# Everything
uv run pytest -vThe tests use copier's Python API with vcs_ref="HEAD", which means copier
generates projects from the last commit on the current branch. If you
modify template files without committing, the tests will run against the
old commit and your changes won't be covered.
Structural tests (test_generation.py, test_options.py,
test_licenses.py) generate projects via copier's Python API and inspect
the output -- file existence, content, template variable substitution. They
are fast (seconds) and need nothing beyond the test dependencies. These
tests must never modify the generated project.
Integration tests (test_integration.py) generate a project, then run
just install, just test, just lint, and just gen-doc via subprocess.
They exercise the full toolchain (uv, linkml, just) and take minutes.
Generating a project with copier takes 1-3 seconds. With 80+ structural tests, per-test generation would be very slow. To avoid this:
- Session-scoped fixtures (in
conftest.py) generate each project variant once and share it across all structural tests. The trade-off: structural tests must treat the generated project as read-only. - Module-scoped fixture for integration tests generates a fresh project
because
justcommands mutate the project directory (installing packages, generating files).
Available session fixtures: default_project, no_example_project,
no_pypi_project, no_docs_preview_project, and license_project
(parametrized across all six license types).
| Helper | Purpose |
|---|---|
generate_project(dest, data_overrides) |
Call copier's run_copy() with sensible defaults |
git_init(project_dir) |
Init git + initial commit (needed for dynamic versioning) |
run_just(project_dir, *args) |
Run a just command via subprocess with timeout |
DEFAULT_DATA |
Dict of template variable defaults used by all tests |
ALL_LICENSES |
List of all six supported license identifiers |
- Pick the right fixture. If you need the default project, use
default_project. If you need a specific option combination that doesn't exist yet, add a new session-scoped fixture inconftest.py. - Put the test in the appropriate module (
test_generation.pyfor general structure,test_options.pyfor boolean flags,test_licenses.pyfor license variants). - Never modify files inside the generated project directory -- session fixtures are shared.
Add the test to test_integration.py. It receives the integration_project
fixture which already has just install run in it, so dependencies are
available. Mark the test (or the whole module) with @pytest.mark.integration.
The GitHub Actions workflow test-template.yml runs two jobs:
- structural -- fast, Python version matrix, Ubuntu only
- integration -- slow, OS matrix (Ubuntu + Windows), Python version matrix
Structural tests run on every push and PR. Integration tests also run on every push and PR but take longer, so they use a smaller matrix focused on OS coverage.