Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions .github/workflows/integration-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Integration CI

on:
workflow_call:

jobs:
prepare-data:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/[email protected]

- name: Restore integration data
id: cache-data
uses: actions/cache@v4
with:
path: tests/data
key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py')}}

- name: Generate data
if: steps.cache-data.outputs.cache-hit != 'true'
run: |
sudo apt-get update && sudo apt-get install -y libsndfile1
uv run tests/setup_integration_data.py
26 changes: 21 additions & 5 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ on:
push:
paths:
- "python/**"
- "tests/**"
- "tests/python/**"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/python-ci.yml"
- ".github/workflows/integration-ci.yml"
- "tests/setup_integration_data.py"
pull_request:
paths:
- "python/**"
- "tests/**"
- "tests/python/**"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/python-ci.yml"
- ".github/workflows/integration-ci.yml"
- "tests/setup_integration_data.py"

jobs:
prepare-data:
uses: ./.github/workflows/integration-ci.yml

build:
runs-on: ubuntu-latest
needs: prepare-data
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
Expand Down Expand Up @@ -47,9 +55,17 @@ jobs:
- name: Run type checks
run: uv run ty check

- name: Run tests
run: |
uv run pytest
- name: Run unit tests
run: uv run pytest python/tests

- name: Restore integration data
uses: actions/cache@v4
with:
path: tests/data
key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }}

- name: Run integration tests
run: uv run pytest tests/python

- name: Run safety checks
run: uv audit
Expand Down
48 changes: 35 additions & 13 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,44 @@ on:
push:
paths:
- "src/**"
- "tests/rust/**"
- ".github/workflows/rust-ci.yml"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/integration-ci.yml"
- "rust-toolchain.toml"
- "tests/setup_integration_data.py"
pull_request:
paths:
- "src/**"
- "tests/rust/**"
- ".github/workflows/rust-ci.yml"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/integration-ci.yml"
- "rust-toolchain.toml"
- "tests/setup_integration_data.py"

env:
CARGO_TERM_COLOR: always

jobs:
prepare-data:
uses: ./.github/workflows/integration-ci.yml

build:
name: Rust - latest
needs: prepare-data
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install latest Rust version
run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- name: Install Rust
run: |
rustup toolchain install
rustup component add rustfmt clippy

- name: rust-cache
uses: Swatinem/rust-cache@v2
Expand All @@ -51,31 +61,43 @@ jobs:
- name: Build
run: cargo build --verbose

- name: Run tests
run: cargo test --verbose
- name: Run unit tests
run: cargo test --lib --verbose

- name: Restore integration data
uses: actions/cache@v4
with:
path: tests/data
key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }}

- name: Run integration tests
run: cargo test --test rust --frozen

- name: Audit dependencies
run: cargo audit

coverage:
if: github.event_name == 'pull_request'
name: Rust coverage
needs: prepare-data
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install latest Rust version
run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
run: rustup toolchain install

- name: rust-cache
uses: Swatinem/rust-cache@v2

- name: Restore integration data
uses: actions/cache@v4
with:
path: tests/data
key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }}

- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main

Expand Down
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@
!python/adaptif/*.py
!python/adaptif/*.pyi

# python unit tests
!python/tests/
!python/tests/**/*.py

# integration tests specifics
!tests/
!tests/**/
!tests/**/*.rs
!tests/**/*.py
!tests/**/*.wav

!examples/
!examples/**/
!examples/**/*.rs

!Cargo.toml
!Cargo.lock
!rust-toolchain.toml

# uv
!pyproject.toml
Expand All @@ -39,5 +47,6 @@
!.github/**/*.md
!.pre-commit-config.yaml

# explitic excludes
# explicit excludes
**/__pycache__/
**/data
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "adaptif"
version = "0.2.0"
edition = "2024"
rust-version = "1.99.0"

[dependencies]
num-traits = "0.2.19" # TODO: use "libm" feature for no_std
Expand All @@ -16,6 +17,7 @@ crate-type = ["cdylib", "rlib"]
default = []
python = ["dep:pyo3", "dep:numpy"]


[lints.clippy]
pedantic = {level = "warn", priority = -1}
restriction = {level = "warn", priority = -1}
Expand Down Expand Up @@ -62,3 +64,6 @@ missing_inline_in_public_items = "allow"
# This one is supposed to skip test modules (#[cfg(test)]), but as of 1.98.0 it only works on nightly.
inline_modules = "allow"

[dev-dependencies]
hound = "3.5.1"

31 changes: 22 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ check-installs: check-rust-install check-uv-install
# Install/update tooling and dependencies
.PHONY: install-rust install-uv setup-rust setup-uv setup
install-rust:
@command rustup --version >/dev/null 2>&1 && \
rustup update || \
@command rustup --version >/dev/null 2>&1 || \
{ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh; }
rustup toolchain install
rustup component add rustfmt clippy
install-uv:
@command uv --version >/dev/null 2>&1 && \
uv self update || \
Expand Down Expand Up @@ -62,17 +63,29 @@ check-py: $(PY_BINDINGS)
uv run ty check
check-all: check-rs check-py

##################################################
# TESTS
##################################################

.PHONY: setup-data test-rs test-py test-all

# Run tests
.PHONY: test-rs test-py test-all
test-rs: check-rust-install
setup-data:
uv run tests/setup_integration_data.py # going to switch to .sh

# Rust
test-rs: check-rust-install setup-data
# NOTE: `--all-features` currently produces a linker error, smth to do with PyO3.
# Since we don't have any tests in the Python features, leaving it out for now.
cargo tarpaulin --frozen --skip-clean
test-py: $(PY_BINDINGS)
uv run pytest
test-all: test-rs test-py
cargo tarpaulin --lib --frozen --skip-clean
cargo test --test rust --frozen

# Python
test-py: $(PY_BINDINGS) setup-data
uv run pytest python/tests
uv run pytest tests/python

# Test type grouping
test-all: test-rs test-py

# Audit dependencies
.PHONY: audit
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dev = [
"ruff>=0.16.4",
"ty>=0.0.75",
"maturin>=1.15.0",
"soundfile>=0.14.0",
]

[tool.maturin]
Expand Down Expand Up @@ -75,7 +76,7 @@ include = [
[tool.pytest.ini_options]
# https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml
# Directories that are not visited by pytest collector:
norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"]
norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__", "tests/data"]
doctest_optionflags = ["NUMBER", "NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]

# Extra options:
Expand Down
Empty file added python/tests/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.99.0"
4 changes: 4 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Integration tests


*TODO: Add more information on tests and what they calculate...*
Empty file added tests/python/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions tests/python/test_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import numpy as np
import pytest
from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter

from .utils import load_test_signals, mse, snr


# placing in realistic values that would actually be used
@pytest.fixture(
params=[
(LMSFilter, {"mu": 0.01, "window_size": 16}),
(NLMSFilter, {"mu": 0.001, "eps": 1e-6, "window_size": 16}),
(
RLSFilter,
{"forgetting_factor": 0.999, "p_init_scale": 0.001, "window_size": 16},
),
(BlockLMSFilter, {"mu": 1e-4, "window_size": 32, "block_size": 32}),
],
ids=["lms", "nlms", "rls", "block-lms"],
)
def filter(request):
filter_class, kwargs = request.param
return filter_class(**kwargs)


def test_filter_adapts_signal(filter) -> None:
original_signal, noisy_signal, noise_reference = load_test_signals()

cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference)

assert cleaned_signal.shape == noisy_signal.shape
assert np.all(np.isfinite(cleaned_signal))

before_snr = snr(original_signal=original_signal, noisy_signal=noisy_signal)
after_snr = snr(original_signal=original_signal, noisy_signal=cleaned_signal)
assert after_snr > before_snr

# NOTE: point of this is that it doesn't only remove noise, but recovers the signal
before_mse = mse(original_signal=original_signal, input_signal=noisy_signal)
after_mse = mse(original_signal=original_signal, input_signal=cleaned_signal)
assert after_mse < before_mse
Loading
Loading