From 6609feceb2fd8f66405bbf9f9d39b40267568eb1 Mon Sep 17 00:00:00 2001 From: Joao Felipe Rocha Date: Tue, 21 Oct 2025 12:47:55 -0400 Subject: [PATCH] Updated the package to use pytest instead of nose2 --- .github/workflows/run_tests.yml | 2 +- pytest.ini | 17 +++++++++++++++++ setup.cfg | 4 ++++ setup.py | 5 ++--- test/load_tests/__init__.py | 20 +++++++++++++++----- test/test_data.py | 11 +++++------ test/test_estimator.py | 2 +- test/test_exact.py | 12 ++++++------ test/test_knn.py | 18 ++++++++++-------- test/test_landmark.py | 1 - test/test_matrix.py | 6 +++--- test/test_mnn.py | 1 - test/test_random_landmarking.py | 1 - unittest.cfg | 6 ------ 14 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 pytest.ini delete mode 100644 unittest.cfg diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 537a885..666c183 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -60,7 +60,7 @@ jobs: - name: Run tests run: | - nose2 -vvv + pytest - name: Coveralls env: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2e594c4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,17 @@ +[pytest] +testpaths = test +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + -v + --strict-markers + --tb=short +markers = + slow: marks tests as slow (deselect with '-m "not slow"') +filterwarnings = + error + ignore::PendingDeprecationWarning:.*matrix subclass.* + ignore::DeprecationWarning:.*SafeConfigParser.* + ignore::DeprecationWarning:.*collections.* + ignore::DeprecationWarning:.*check_pickle.* diff --git a/setup.cfg b/setup.cfg index c4ba0c6..1d2850f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,3 +27,7 @@ exclude = profile = black force_single_line = true force_alphabetical_sort = true + +[coverage] +always-on = True +coverage = graphtools diff --git a/setup.py b/setup.py index acc15ee..05b6ac2 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ ] test_requires = [ - "nose", - "nose2", + "pytest", + "pytest-cov", "pandas", "coverage", "coveralls", @@ -64,7 +64,6 @@ "fast": fast_requires, "all": all_requires, }, - test_suite="nose2.collector.collector", long_description=readme, url="https://github.com/KrishnaswamyLab/graphtools", download_url="https://github.com/KrishnaswamyLab/graphtools/archive/v{}.tar.gz".format( diff --git a/test/load_tests/__init__.py b/test/load_tests/__init__.py index 96994e4..01b7fd4 100644 --- a/test/load_tests/__init__.py +++ b/test/load_tests/__init__.py @@ -1,5 +1,3 @@ -from nose.tools import assert_raises_regex -from nose.tools import assert_warns_regex from scipy.spatial.distance import cdist from scipy.spatial.distance import pdist from scipy.spatial.distance import squareform @@ -8,10 +6,10 @@ from sklearn.decomposition import TruncatedSVD import graphtools -import nose2 import numpy as np import pandas as pd import pygsp +import pytest import re import scipy.sparse as sp import warnings @@ -19,12 +17,24 @@ def assert_warns_message(expected_warning, expected_message, *args, **kwargs): expected_regex = re.escape(expected_message) - return assert_warns_regex(expected_warning, expected_regex, *args, **kwargs) + if args: + # If function arguments are provided, call the function within the context + with pytest.warns(expected_warning, match=expected_regex): + return args[0](*args[1:], **kwargs) + else: + # Return the context manager to be used with 'with' statement + return pytest.warns(expected_warning, match=expected_regex) def assert_raises_message(expected_warning, expected_message, *args, **kwargs): expected_regex = re.escape(expected_message) - return assert_raises_regex(expected_warning, expected_regex, *args, **kwargs) + if args: + # If function arguments are provided, call the function within the context + with pytest.raises(expected_warning, match=expected_regex): + return args[0](*args[1:], **kwargs) + else: + # Return the context manager to be used with 'with' statement + return pytest.raises(expected_warning, match=expected_regex) def reset_warnings(): diff --git a/test/test_data.py b/test/test_data.py index ea7056a..f0cfdcf 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -5,15 +5,14 @@ from load_tests import build_graph from load_tests import data from load_tests import graphtools -from load_tests import nose2 from load_tests import np from load_tests import pd from load_tests import pdist from load_tests import sp from load_tests import squareform -from nose.tools import assert_raises_regex import numbers +import pytest import warnings try: @@ -99,9 +98,9 @@ def test_negative_rank_threshold(): def test_True_n_pca_large_threshold(): - with assert_raises_regex( + with pytest.raises( ValueError, - r"Supplied threshold ([0-9\.]*) was greater than maximum singular value ([0-9\.]*) for the data matrix", + match=r"Supplied threshold ([0-9\.]*) was greater than maximum singular value ([0-9\.]*) for the data matrix", ): build_graph(data, n_pca=True, rank_threshold=np.linalg.norm(data) ** 2) @@ -388,10 +387,10 @@ def test_inverse_transform_sparse_svd(): # Flexible regex pattern that works across Python versions sparse_error_pattern = r"(Sparse data|A sparse matrix) was passed, but dense data is required\. Use (?:'.*?'|X\.toarray\(\)) to convert to a dense numpy array\." - with assert_raises_regex(TypeError, sparse_error_pattern): + with pytest.raises(TypeError, match=sparse_error_pattern): G.inverse_transform(sp.csr_matrix(G.data)[:, 0]) - with assert_raises_regex(TypeError, sparse_error_pattern): + with pytest.raises(TypeError, match=sparse_error_pattern): G.inverse_transform(sp.csr_matrix(G.data)[:, :15]) with assert_raises_message( diff --git a/test/test_estimator.py b/test/test_estimator.py index bdac8e7..9de624f 100644 --- a/test/test_estimator.py +++ b/test/test_estimator.py @@ -42,7 +42,7 @@ def test_estimator(): assert E.graph is None -@parameterized( +@parameterized.expand( [ ("precomputed", 1 - np.eye(10), "distance"), ("precomputed", np.eye(10), "affinity"), diff --git a/test/test_exact.py b/test/test_exact.py index 43bfd12..ebde1b3 100644 --- a/test/test_exact.py +++ b/test/test_exact.py @@ -5,7 +5,6 @@ from load_tests import build_graph from load_tests import data from load_tests import graphtools -from load_tests import nose2 from load_tests import np from load_tests import PCA from load_tests import pdist @@ -13,7 +12,8 @@ from load_tests import sp from load_tests import squareform from load_tests import TruncatedSVD -from nose.tools import assert_warns_regex + +import pytest from scipy.sparse.csgraph import shortest_path ##################################################### @@ -98,17 +98,17 @@ def test_precomputed_nonzero_diagonal(): def test_duplicate_data(): - with assert_warns_regex( + with pytest.warns( RuntimeWarning, - r"Detected zero distance between samples ([0-9and,\s]*). Consider removing duplicates to avoid errors in downstream processing.", + match=r"Detected zero distance between samples ([0-9and,\s]*). Consider removing duplicates to avoid errors in downstream processing.", ): build_graph(np.vstack([data, data[:10]]), n_pca=20, decay=10, thresh=0) def test_many_duplicate_data(): - with assert_warns_regex( + with pytest.warns( RuntimeWarning, - "Detected zero distance between ([0-9]*) pairs of samples. Consider removing duplicates to avoid errors in downstream processing.", + match=r"Detected zero distance between ([0-9and,\s]*) pairs of samples. Consider removing duplicates to avoid errors in downstream processing.", ): build_graph(np.vstack([data, data]), n_pca=20, decay=10, thresh=0) diff --git a/test/test_knn.py b/test/test_knn.py index 71bf9ec..0cb4442 100644 --- a/test/test_knn.py +++ b/test/test_knn.py @@ -12,9 +12,10 @@ from load_tests import pygsp from load_tests import sp from load_tests import TruncatedSVD -from nose.tools import assert_raises_regex -from nose.tools import assert_warns_regex + +import pytest from scipy.sparse.csgraph import shortest_path + from scipy.spatial.distance import pdist from scipy.spatial.distance import squareform @@ -50,17 +51,17 @@ def test_build_knn_with_sample_idx(): def test_duplicate_data(): - with assert_warns_regex( + with pytest.warns( RuntimeWarning, - r"Detected zero distance between samples ([0-9and,\s]*). Consider removing duplicates to avoid errors in downstream processing.", + match=r"Detected zero distance between samples ([0-9and,\s]*). Consider removing duplicates to avoid errors in downstream processing.", ): build_graph(np.vstack([data, data[:9]]), n_pca=None, decay=10, thresh=1e-4) def test_duplicate_data_many(): - with assert_warns_regex( + with pytest.warns( RuntimeWarning, - "Detected zero distance between ([0-9]*) pairs of samples. Consider removing duplicates to avoid errors in downstream processing.", + match=r"Detected zero distance between ([0-9and,\s]*) pairs of samples. Consider removing duplicates to avoid errors in downstream processing.", ): build_graph(np.vstack([data, data[:21]]), n_pca=None, decay=10, thresh=1e-4) @@ -305,8 +306,9 @@ def test_knnmax(): ) assert isinstance(G2, graphtools.graphs.kNNGraph) assert G.N == G2.N - assert np.all(G.dw == G2.dw) - assert (G.W - G2.W).nnz == 0 + np.testing.assert_allclose(G.dw, G2.dw) + # Use allclose for sparse matrices to handle floating-point precision + np.testing.assert_allclose(G.W.toarray(), G2.W.toarray(), rtol=1e-7, atol=1e-10) finally: gg.NUMBA_AVAILABLE = original_numba diff --git a/test/test_landmark.py b/test/test_landmark.py index facdffe..aaeb929 100644 --- a/test/test_landmark.py +++ b/test/test_landmark.py @@ -7,7 +7,6 @@ from load_tests import digits from load_tests import generate_swiss_roll from load_tests import graphtools -from load_tests import nose2 from load_tests import np import pygsp diff --git a/test/test_matrix.py b/test/test_matrix.py index 41cf757..e19502e 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -9,7 +9,7 @@ import numpy as np -@parameterized( +@parameterized.expand( [ (np.array,), (sparse.csr_matrix,), @@ -26,13 +26,13 @@ def test_nonzero_discrete(matrix_class): assert not graphtools.matrix.nonzero_discrete(X, [1, 3]) -@parameterized([(0,), (1e-4,)]) +@parameterized.expand([(0,), (1e-4,)]) def test_nonzero_discrete_knngraph(thresh): G = graphtools.Graph(data, n_pca=10, knn=5, decay=None, thresh=thresh) assert graphtools.matrix.nonzero_discrete(G.K, [0.5, 1]) -@parameterized([(0,), (1e-4,)]) +@parameterized.expand([(0,), (1e-4,)]) def test_nonzero_discrete_decay_graph(thresh): G = graphtools.Graph(data, n_pca=10, knn=5, decay=15, thresh=thresh) assert not graphtools.matrix.nonzero_discrete(G.K, [0.5, 1]) diff --git a/test/test_mnn.py b/test/test_mnn.py index df83b50..c0af828 100644 --- a/test/test_mnn.py +++ b/test/test_mnn.py @@ -8,7 +8,6 @@ from load_tests import digits from load_tests import generate_swiss_roll from load_tests import graphtools -from load_tests import nose2 from load_tests import np from load_tests import pd from load_tests import pygsp diff --git a/test/test_random_landmarking.py b/test/test_random_landmarking.py index 4142eb0..329fb98 100644 --- a/test/test_random_landmarking.py +++ b/test/test_random_landmarking.py @@ -7,7 +7,6 @@ from load_tests import digits from load_tests import generate_swiss_roll from load_tests import graphtools -from load_tests import nose2 from load_tests import np import pygsp diff --git a/unittest.cfg b/unittest.cfg deleted file mode 100644 index 85c81ba..0000000 --- a/unittest.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[unittest] -verbose = True - -[coverage] -always-on = True -coverage = graphtools