diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b0b6a9a..e843841 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,5 +31,14 @@ jobs:
else
uv python install
uv sync --extra test
- uv run pytest -n auto --tb=short
+ uv run pytest -n auto --tb=short \
+ --cov=delaynet --cov-branch \
+ --cov-report=xml --cov-report=term-missing
fi
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ files: ./coverage.xml
+ token: ${{ secrets.CODECOV_TOKEN }}
+ if: matrix.python-version == 'latest'
diff --git a/README.md b/README.md
index 0604646..953d14f 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@
[](https://github.com/cbueth/delaynet/actions/workflows/test.yml)
[](https://github.com/cbueth/delaynet/actions/workflows/lint.yml)
[](https://app.codspeed.io//cbueth/delaynet?utm_source=badge)
+[](https://codecov.io/gh/cbueth/delaynet)
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..47d75c2
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,13 @@
+coverage:
+ status:
+ project:
+ default:
+ target: auto
+ threshold: 2%
+ patch:
+ default:
+ target: 100%
+
+comment:
+ layout: header, diff
+ behavior: default
diff --git a/delaynet/network_analysis/metrics.py b/delaynet/network_analysis/metrics.py
index 60153a7..1e9a2ec 100644
--- a/delaynet/network_analysis/metrics.py
+++ b/delaynet/network_analysis/metrics.py
@@ -116,8 +116,7 @@ def betweenness_centrality(
# For undirected graphs: max betweenness = (n-1)*(n-2)/2
max_betweenness = (n_nodes - 1) * (n_nodes - 2) / 2.0
- if max_betweenness > 0:
- centrality = centrality / max_betweenness
+ centrality = centrality / max_betweenness
return centrality
diff --git a/tests/connectivities/test_granger.py b/tests/connectivities/test_granger.py
index 56c6136..ae3d16c 100644
--- a/tests/connectivities/test_granger.py
+++ b/tests/connectivities/test_granger.py
@@ -152,3 +152,27 @@ def test_stronger_causal_signal_lower_pvalue(rng):
assert p_strong < p_weak, (
f"Stronger signal should have lower p-value (strong: {p_strong}, weak: {p_weak})"
)
+
+
+def test_gt_single_lag_lstsq_fallback():
+ """Test lstsq fallback when solve raises LinAlgError at line 92."""
+ from unittest.mock import patch
+
+ ts1 = np.random.normal(0, 1, size=100)
+ ts2 = np.random.normal(0, 1, size=100)
+
+ original_solve = np.linalg.solve
+ solve_calls = []
+
+ def mock_solve(a, b):
+ solve_calls.append((a, b))
+ if len(solve_calls) == 2:
+ raise np.linalg.LinAlgError(
+ "Simulating singular R_vcov_R for test coverage"
+ )
+ return original_solve(a, b)
+
+ with patch("numpy.linalg.solve", mock_solve):
+ p_value = gt_single_lag(ts1, ts2, lag_step=2)
+ assert isinstance(p_value, float)
+ assert 0 <= p_value <= 1
diff --git a/tests/test_network_reconstruction.py b/tests/test_network_reconstruction.py
index be56179..13b7a83 100644
--- a/tests/test_network_reconstruction.py
+++ b/tests/test_network_reconstruction.py
@@ -1,5 +1,6 @@
"""Tests for the network reconstruction module."""
+import numpy as np
import pytest
from time import time
from numpy import (
@@ -26,6 +27,8 @@
format_time,
print_progress,
update_progress,
+ _compute_pair_connectivity_shared,
+ _compute_with_progress,
)
@@ -663,3 +666,55 @@ def __init__(self, value):
# Verify print_progress was called with explicit parameter
assert called[0] is True
assert sphinx_mode_value[0] is True
+
+
+def test_compute_pair_connectivity_shared():
+ """Test the shared-memory worker function directly."""
+ from multiprocessing import shared_memory
+
+ data = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]])
+ shm = shared_memory.SharedMemory(create=True, size=data.nbytes)
+ shared_array = ndarray(data.shape, dtype=data.dtype, buffer=shm.buf)
+ shared_array[:] = data
+ try:
+ args = (0, 1, shm.name, data.shape, data.dtype, "linear_correlation", 1, {})
+ i, j, p, lag = _compute_pair_connectivity_shared(args)
+ assert i == 0
+ assert j == 1
+ assert isinstance(p, float)
+ assert isinstance(lag, int)
+ finally:
+ shm.close()
+ shm.unlink()
+
+
+def test_compute_with_progress(monkeypatch):
+ """Test the _compute_with_progress worker wrapper."""
+ from multiprocessing import shared_memory, Manager
+
+ monkeypatch.setattr("sys.stdout.write", lambda x: None)
+ monkeypatch.setattr("sys.stdout.flush", lambda: None)
+
+ data = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]])
+ shm = shared_memory.SharedMemory(create=True, size=data.nbytes)
+ shared_array = ndarray(data.shape, dtype=data.dtype, buffer=shm.buf)
+ shared_array[:] = data
+ try:
+ args = (0, 1, shm.name, data.shape, data.dtype, "linear_correlation", 1, {})
+ with Manager() as manager:
+ counter = manager.Value("i", 0)
+ lock = manager.Lock()
+ result = _compute_with_progress(
+ args,
+ counter,
+ lock,
+ total_pairs=1,
+ start_time=time(),
+ sphinx_mode=True,
+ )
+ assert result is not None
+ assert result[0] == 0
+ assert result[1] == 1
+ finally:
+ shm.close()
+ shm.unlink()