Skip to contents

This project uses testthat and devtools to test the installed ARTEMIS package — including its R to Python to Cython bridge logic.

Structure

tests/
├── testthat/
│   ├── helper.R # test utilities, env setup
│   ├── test-000-smoke.R # baseline smoke test
│   ├── test-100-bridge.R # bridge output consistency check
...
│   └── testthat.yml # optional (reporter config)
└── testthat.R # testthat entrypoint (required)

Test Workflow

ARTEMIS must be installed and its install location added to .libPaths() before running tests.

.libPaths(c("/path/to/ARTEMIS", .libPaths()))
library(devtools)
library(testthat)
library(ARTEMIS)

devtools::test(pkg = "/path/to/ARTEMIS/ARTEMIS")

Where last ARTEMIS refers to the actual package directory Parent dir might contain other build/test infra

What Gets Tested

  • 000-smoke.R

Asserts testthat itself is wired and functional.

  • 100-bridge.R

Uses reticulate::import_from_path() to load:

cython/main.py

python/main.py

Then checks their return values match using:

isTRUE(all.equal(r_cy, r_py, tolerance = 1e-8))

R↔︎Python Boundary

Scope (Exploration → Decisions)
1. Canonical interface (R side)
A single exported R function is the only supported entrypoint for calling Python. All R code must go through this function instead of calling reticulate directly.

  1. Mocking strategy
    The Python call can be replaced with a pure R mock so unit tests do not depend on reticulate or a Python runtime. Python and Cython are treated as interchangeable real implementations and are exercised only in integration tests.

  2. Serialization boundary
    The interaction is treated as a strict serialization boundary: no Python objects cross into R, and all inputs and outputs are fully materialized R data structures.

Expected Output

devtools::test(pkg="/path/to/ARTEMIS")
ℹ Testing ARTEMIS
✔ | F W S OK | Context
✔ | 1 | 000-smoke
Processing patients: 100%|█| 1/1 [00:00<00:00, 1226.76

... [bridge output showing processed tables] ...

Cython module loaded successfully.
✔ | 1 | 100-bridge

══ Results ═══════════════════════════════════════════
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ]

Warning message:
Objects listed as exports, but not present in
namespace:
• calculateEras
• combineOverlaps
...

Post-Test Cleanup

To fix the NAMESPACE export warning:

devtools::document()

This will regenerate the NAMESPACE file based on roxygen tags (#’ @export) in your R code.

Use devtools::check() When:

  • You’re releasing

  • You want full CRAN-style checks

  • You want to catch broken docs, missing imports, export issues, etc.

Use devtools::test() When:

  • You’re actively developing

  • You want fast, focused feedback

  • You’re iterating on R or Python bridge logic


Conventional Commits

ARTEMIS adopts the Conventional Commits specification for all commit messages. The format is enforced locally by the commit-msg hook and mandatorily by the lint-commits CI job on every PR.

Format

<type>(<scope>): <short description>

[optional body]

[optional footer — BREAKING CHANGE: <description>]

Allowed types

Type When to use in ARTEMIS
feat New alignment feature, new exported R function
fix Bug in alignment, scoring, plotting, or I/O
chore Dependency bumps, data updates, build scripts
docs README, man/*.Rd, vignettes
style Formatting only — no logic change
refactor Code restructure with no behaviour change
test testthat additions or fixes
ci GitHub Actions workflow changes
perf Algorithm performance improvements

Scope vocabulary

r-bridge   cython   scoring   preprocess   plots
data       hooks    ci        packaging

Scope is optional but encouraged. cython and similar labels are purely descriptive — they do not imply the contributor needs to touch Python/Cython.

Breaking changes

Append ! after the type, or add a BREAKING CHANGE: footer:

feat(r-bridge)!: rename generateRawAlignments argument order

BREAKING CHANGE: the `df` parameter is now the second argument, not the first.

Worked examples

feat(cython): pass gap-open/extend params to C extension
fix(r-bridge): guard against empty alignment output
ci(docker): add arm64 image build workflow
chore(data): update regimen reference data 2025
perf(scoring): remove redundant max in TSW score matrix
docs(vignettes): add Gitflow branch topology section
test(r-bridge): add testthat case for zero-drug patient
refactor(preprocess): extract cleanText into its own function

What the commit-msg hook enforces (locally, opt-in)

The hook at .githooks/commit-msg rejects any message that does not match:

^(feat|fix|chore|docs|style|refactor|test|ci|perf)(\(.+\))?: .+

It skips merge commits and rebase operations automatically.

Activate with:

git config core.hooksPath .githooks

What CI enforces (mandatory, every PR)

The lint-commits job in .github/workflows/lint.yml runs:

cz check --rev-range origin/develop..HEAD

This validates every individual commit in the PR — not just the PR title. The PR title is additionally validated by amannn/action-semantic-pull-request because it becomes the squash-merge commit message.


Release workflow (cz bump)

The only person who needs commitizen installed locally is whoever cuts the release. Everyone else: just git + R.

pip install commitizen   # once, on the release manager's machine

# On the release/* branch:
cz bump          # reads commits since last tag, bumps DESCRIPTION Version,
                 # updates CHANGELOG.md, creates a git tag (e.g. v1.5.0)

git push && git push --tags

cz bump rewrites the Version: field in DESCRIPTION according to semver (patch / minor / major determined by commit types — feat → minor, fix/perf → patch, BREAKING CHANGE → major).

The tag format is v$version (e.g. v1.5.0), configured in .cz.toml.

How commit validation works

.cz.toml is the single source of truth for commit rules and versioning:

  • cz check (run by CI) reads allowed types from .cz.toml
  • cz bump reads the commit log and decides the next version
  • The commit-msg hook uses the same type list embedded as a regex

Contributors never need to read .cz.toml directly — the type list above and the examples in this section are sufficient.