Skip to content

Commit a7d71b0

Browse files
committed
feat(test): Added python test to code coverage
1 parent 488ed51 commit a7d71b0

6 files changed

Lines changed: 111 additions & 10 deletions

File tree

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
blank_issues_enabled: false
22
contact_links:
33
- name: Ask a question
4-
url: https://github.com/bebr/simstring_rs/discussions/new
4+
url: https://github.com/PyDataBlog/simstring_rs/discussions/new
55
about: Please ask and answer questions here.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ about: Propose a change to the project
44
title: "feat(scope): describe your change"
55
labels: ""
66
assignees: ""
7+
78
---
89

910
**Description**

.github/workflows/coverage.yml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,21 @@ jobs:
3131
with:
3232
python-version: "3.13"
3333

34-
- name: Install cargo-tarpaulin
35-
run: cargo install cargo-tarpaulin --version 0.26.0 --force
36-
37-
- name: Set Python interpreter for pyo3
38-
run: echo "PYTHON_SYS_EXECUTABLE=$(which python3)" >> $GITHUB_ENV
34+
- name: Install grcov
35+
run: cargo install grcov --force
3936

4037
- name: Run tests and generate coverage report
4138
run: |
42-
LIBDIR=$(python3 -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")
43-
VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
44-
export RUSTFLAGS="-L $LIBDIR -lpython$VERSION"
45-
cargo tarpaulin --all-features --out Xml --output-dir ./coverage
39+
# Set flags for coverage generation
40+
export CARGO_INCREMENTAL=0
41+
export RUSTFLAGS="-Cinstrument-coverage"
42+
# Set the path for the raw coverage data
43+
export LLVM_PROFILE_FILE="target/coverage/simstring_rs-%p-%m.profraw"
44+
# Run all tests, including the ignored python bindings
45+
cargo test --all-features -- --ignored
46+
# Generate the coverage report
47+
grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" "tests/*" -o ./coverage.lcov
48+
grcov . --binary-path ./target/debug/ -s . -t cobertura --branch --ignore-not-existing --ignore "/*" "tests/*" -o ./coverage/cobertura.xml
4649
4750
- name: Upload coverage to Codecov
4851
uses: codecov/codecov-action@v5

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
[![Build Status](https://github.com/PyDataBlog/simstring_rs/actions/workflows/CI.yml/badge.svg)](https://github.com/PyDataBlog/simstring_rs/actions)
44
[![Crates.io](https://img.shields.io/crates/v/simstring_rust.svg)](https://crates.io/crates/simstring_rust)
5+
[![PyPI version](https://badge.fury.io/py/simstring-rust.svg)](https://badge.fury.io/py/simstring-rust)
6+
[![Python versions](https://img.shields.io/pypi/pyversions/simstring-rust.svg)](https://pypi.org/project/simstring-rust)
57
[![Documentation](https://docs.rs/simstring_rust/badge.svg)](https://docs.rs/simstring_rust)
68
[![Rust](https://img.shields.io/badge/rust-1.63.0%2B-blue.svg?maxAge=3600)](https://github.com/PyDataBlog/simstring_rs)
79
[![Codecov](https://img.shields.io/codecov/c/github/PyDataBlog/simstring_rs?token=XJM8O8TD4U)](https://codecov.io/gh/PyDataBlog/simstring_rs)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ classifiers = [
2020
"Programming Language :: Python :: 3.10",
2121
"Programming Language :: Python :: 3.11",
2222
"Programming Language :: Python :: 3.12",
23+
"Programming Language :: Python :: 3.13",
2324
"License :: OSI Approved :: MIT License",
2425
"Operating System :: POSIX",
2526
"Operating System :: MacOS :: MacOS X",

tests/test_python_integration.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
use std::process::Command;
4+
5+
#[test]
6+
#[ignore]
7+
fn run_python_tests() {
8+
let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
9+
let venv_dir = project_root.join("target").join("pytest_venv");
10+
let python_executable_path;
11+
let maturin_executable_path;
12+
let pytest_executable_path;
13+
14+
if cfg!(windows) {
15+
python_executable_path = venv_dir.join("Scripts").join("python.exe");
16+
maturin_executable_path = venv_dir.join("Scripts").join("maturin.exe");
17+
pytest_executable_path = venv_dir.join("Scripts").join("pytest.exe");
18+
} else {
19+
python_executable_path = venv_dir.join("bin").join("python");
20+
maturin_executable_path = venv_dir.join("bin").join("maturin");
21+
pytest_executable_path = venv_dir.join("bin").join("pytest");
22+
}
23+
24+
// Create virtual environment if it doesn't exist
25+
if !venv_dir.exists() {
26+
println!("--- Creating Python virtual environment in `target` directory ---");
27+
let venv_cmd = Command::new("python3")
28+
.args(["-m", "venv", venv_dir.to_str().unwrap()])
29+
.status()
30+
.expect("Failed to execute `python3 -m venv`. Is `python3` in your PATH?");
31+
if !venv_cmd.success() {
32+
panic!("Failed to create python venv. Is `python3` and `venv` module installed?");
33+
}
34+
}
35+
36+
// Install dependencies into the virtual environment
37+
println!("--- Installing maturin and pytest using pip ---");
38+
let pip_cmd = Command::new(python_executable_path.to_str().unwrap())
39+
.args(["-m", "pip", "install", "-U", "pip", "maturin", "pytest"])
40+
.status()
41+
.expect("Failed to run pip install. Is the venv corrupted?");
42+
if !pip_cmd.success() {
43+
panic!("Failed to install maturin and pytest in the venv.");
44+
}
45+
46+
// Build the python wheel
47+
println!("--- Building wheel with maturin ---");
48+
let maturin_build_cmd = Command::new(maturin_executable_path.to_str().unwrap())
49+
.args(["build", "--release", "--out", "target/wheels"])
50+
.status()
51+
.expect("Failed to run `maturin build`.");
52+
if !maturin_build_cmd.success() {
53+
panic!("`maturin build` failed.");
54+
}
55+
56+
// Install the generated Python wheel
57+
println!("--- Installing wheel with pip ---");
58+
let wheel_path = std::fs::read_dir("target/wheels")
59+
.unwrap()
60+
.filter_map(|entry| entry.ok())
61+
.find(|entry| {
62+
entry
63+
.path()
64+
.extension()
65+
.map_or_else(|| false, |ext| ext == "whl")
66+
})
67+
.unwrap()
68+
.path();
69+
70+
let pip_install_wheel_cmd = Command::new(python_executable_path.to_str().unwrap())
71+
.args([
72+
"-m",
73+
"pip",
74+
"install",
75+
wheel_path.to_str().unwrap(),
76+
"--force-reinstall",
77+
])
78+
.status()
79+
.expect("Failed to install wheel.");
80+
if !pip_install_wheel_cmd.success() {
81+
panic!("Failed to install wheel.");
82+
}
83+
84+
// Run pytest
85+
println!("--- Running pytest ---");
86+
let pytest_cmd = Command::new(pytest_executable_path.to_str().unwrap())
87+
.arg("tests/python/")
88+
.status()
89+
.expect("Failed to run `pytest`.");
90+
if !pytest_cmd.success() {
91+
panic!("Pytest failed.");
92+
}
93+
}
94+

0 commit comments

Comments
 (0)