Skip to content

Separate test utils from tests #2235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d787df6
fixturized data
flying-sheep Apr 13, 2022
84d86b3
oops
flying-sheep Apr 13, 2022
884d68a
move mark imports after setup()
flying-sheep Apr 13, 2022
343c929
darn
flying-sheep Apr 13, 2022
36a4b01
relative to test
flying-sheep Apr 14, 2022
601f681
Perfection
flying-sheep Apr 14, 2022
3594084
Merge branch 'master' into test-utils
flying-sheep Jun 19, 2023
b7bd6c8
Fixup
flying-sheep Jun 19, 2023
acb9e92
improve test ids
flying-sheep Jun 19, 2023
1694c65
xfail broken test
flying-sheep Jun 19, 2023
590c69f
Merge branch 'master' into test-utils
flying-sheep Jun 19, 2023
a8910ea
Fixup merge
flying-sheep Jun 19, 2023
0e4d63c
ah right
flying-sheep Jun 19, 2023
5ce5de7
test_pbmc3k no longer failing
flying-sheep Jun 19, 2023
c3aadb4
Explain make_skip_mark
flying-sheep Jun 19, 2023
1671467
simplify paga tests again
flying-sheep Jun 19, 2023
a7b2b1c
back to testpaths
flying-sheep Jun 19, 2023
d90499e
Fix colormap import
flying-sheep Jun 20, 2023
7e1e28f
add token
flying-sheep Jun 20, 2023
e6f771b
make feature not a view
flying-sheep Jun 20, 2023
c1b1aea
Simplify tests by fixturizing more
flying-sheep Jun 20, 2023
f6ea8f8
Merge branch 'master' into test-utils
flying-sheep Jun 20, 2023
3a53779
Discard changes to .azure-pipelines.yml
flying-sheep Jun 20, 2023
6bac462
rely on config
flying-sheep Jun 20, 2023
9e82d5b
typing
flying-sheep Jun 20, 2023
d62d246
terminal report for debugging
flying-sheep Jun 20, 2023
5feb8be
trace
flying-sheep Jun 20, 2023
55a7d47
fixed
flying-sheep Jun 20, 2023
894c2d7
source_pkgs
flying-sheep Jun 20, 2023
ecb3b7f
hopefully fix coverage
flying-sheep Jun 20, 2023
9e21b23
maybe enable module coverage
flying-sheep Jun 20, 2023
5c64a84
don’t install pytest -cov
flying-sheep Jun 20, 2023
82c4e86
needs coverage.py
flying-sheep Jun 20, 2023
731aaef
Use functions for data fixtures (#2522)
flying-sheep Jun 22, 2023
182a834
Unify needs_ marks
flying-sheep Jun 22, 2023
75b9c5f
missed a few
flying-sheep Jun 22, 2023
04cb0a4
missed marks
flying-sheep Jun 22, 2023
2e12e3b
Merge branch 'master' into test-utils
flying-sheep Jun 22, 2023
c664b28
centralize pytest options
flying-sheep Jun 23, 2023
2413a36
De-fixtureized
flying-sheep Jun 23, 2023
58e9f29
Prettification
flying-sheep Jun 23, 2023
d6f44e7
use needs() everywhere
flying-sheep Jun 23, 2023
f9b87d1
oops
flying-sheep Jun 23, 2023
ce668b8
fixed
flying-sheep Jun 23, 2023
6b9e734
enable a few dask tests
flying-sheep Jun 23, 2023
fcb0a06
oops
flying-sheep Jun 23, 2023
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
11 changes: 6 additions & 5 deletions .azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ trigger:
variables:
python.version: '3.9'
PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip
PYTEST_ADDOPTS: '-v --color=yes --nunit-xml=nunit/test-results.xml'
ANNDATA_DEV: no
RUN_COVERAGE: no
TEST_EXTRA: 'test-full'
Expand Down Expand Up @@ -48,14 +49,14 @@ jobs:

- script: |
python -m pip install --upgrade pip
pip install pytest-cov wheel
pip install wheel coverage
pip install .[dev,$(TEST_EXTRA)]
displayName: 'Install dependencies'
condition: eq(variables['PRERELEASE_DEPENDENCIES'], 'no')

- script: |
python -m pip install --pre --upgrade pip
pip install --pre pytest-cov wheel
pip install --pre wheel coverage
pip install --pre .[dev,$(TEST_EXTRA)]
displayName: 'Install dependencies release candidates'
condition: eq(variables['PRERELEASE_DEPENDENCIES'], 'yes')
Expand All @@ -69,13 +70,13 @@ jobs:
pip list
displayName: 'Display installed versions'

- script: |
pytest -v --color=yes --ignore=scanpy/tests/_images --nunit-xml="nunit/test-results.xml"
- script: pytest
displayName: 'PyTest'
condition: eq(variables['RUN_COVERAGE'], 'no')

- script: |
pytest -v --color=yes --ignore=scanpy/tests/_images --nunit-xml="nunit/test-results.xml" --cov=scanpy --cov-report=xml
coverage run -m pytest
coverage xml
displayName: 'PyTest (coverage)'
condition: eq(variables['RUN_COVERAGE'], 'yes')

Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/docs/jupyter_execute

# tests
/*coverage*
/nunit/
/.cache/
/.pytest_cache/
/scanpy/tests/test*.h5ad
Expand Down Expand Up @@ -44,6 +46,3 @@ Thumbs.db
# IDEs and editors
/.idea/
/.vscode/

# Test artifacts
**/test-results.xml
55 changes: 0 additions & 55 deletions conftest.py

This file was deleted.

16 changes: 13 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,23 @@ exclude = [
]

[tool.pytest.ini_options]
python_files = "test_*.py"
testpaths = "scanpy/tests/"
addopts = [
"--import-mode=importlib",
"-pscanpy.testing._pytest",
]
testpaths = ["scanpy"]
ignore = ["scanpy/tests/_images"]
xfail_strict = true
nunit_attach_on = "fail"
markers = [
"internet: tests which rely on internet resources (enable with `--internet-tests`)",
]

[tool.coverage.run]
source = ["scanpy"]
source_pkgs = ["scanpy"]
omit = ["*/tests/*"]
[tool.coverage.paths]
source = [".", "**/site-packages"]

[tool.black]
skip-string-normalization = true
Expand All @@ -169,6 +176,7 @@ select = [
"F", # Pyflakes
"E", # Pycodestyle errors
"W", # Pycodestyle warnings
"TID251", # Banned imports
]
ignore = [
# module imported but unused -> required for Scanpys API
Expand All @@ -185,3 +193,5 @@ ignore = [
[tool.ruff.per-file-ignores]
# Do not assign a lambda expression, use a def
"scanpy/tools/_rank_genes_groups.py" = ["E731"]
[tool.ruff.flake8-tidy-imports.banned-api]
"pytest.importorskip".msg = "Use the “@needs” decorator/mark instead"
4 changes: 2 additions & 2 deletions scanpy/plotting/_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from matplotlib.figure import Figure
from matplotlib.colors import Normalize
from matplotlib import pyplot as pl
from matplotlib import rcParams, cm
from matplotlib import rcParams, colormaps
from anndata import AnnData
from typing import Union, Optional, List, Sequence, Iterable, Mapping, Literal

Expand Down Expand Up @@ -1446,7 +1446,7 @@ def embedding_density(

# Make the color map
if isinstance(color_map, str):
color_map = copy(cm.get_cmap(color_map))
color_map = copy(colormaps.get_cmap(color_map))

color_map.set_over('black')
color_map.set_under('lightgray')
Expand Down
5 changes: 2 additions & 3 deletions scanpy/plotting/_tools/scatterplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from pandas.api.types import is_categorical_dtype
from matplotlib import pyplot as pl, colors
from matplotlib.cm import get_cmap
from matplotlib import pyplot as pl, colors, colormaps
from matplotlib import rcParams
from matplotlib import patheffects
from matplotlib.colors import Colormap, Normalize
Expand Down Expand Up @@ -160,7 +159,7 @@ def embedding(
raise ValueError("Cannot specify both `color_map` and `cmap`.")
else:
cmap = color_map
cmap = copy(get_cmap(cmap))
cmap = copy(colormaps.get_cmap(cmap))
cmap.set_bad(na_color)
kwargs["cmap"] = cmap
# Prevents warnings during legend creation
Expand Down
Empty file added scanpy/testing/__init__.py
Empty file.
35 changes: 7 additions & 28 deletions scanpy/tests/helpers.py → scanpy/testing/_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import scanpy as sc
import numpy as np
import warnings
import pytest
from anndata.tests.helpers import asarray, assert_equal
from scanpy.tests._data._cached_datasets import pbmc3k


# TODO: Report more context on the fields being compared on error
# TODO: Allow specifying paths to ignore on comparison
Expand All @@ -20,7 +19,7 @@
# These functions can be used to check that functions are correctly using arugments like `layers`, `obsm`, etc.


def check_rep_mutation(func, X, *, fields=["layer", "obsm"], **kwargs):
def check_rep_mutation(func, X, *, fields=("layer", "obsm"), **kwargs):
"""Check that only the array meant to be modified is modified."""
adata = sc.AnnData(X=X.copy(), dtype=X.dtype)
for field in fields:
Expand Down Expand Up @@ -87,32 +86,12 @@ def check_rep_results(func, X, *, fields=["layer", "obsm"], **kwargs):
assert_equal(adata_X, adatas_proc[field])


def _prepare_pbmc_testdata(sparsity_func, dtype, small=False):
"""Prepares 3k PBMC dataset with batch key `batch` and defined datatype/sparsity.

Params
------
sparsity_func
sparsity function applied to adata.X (e.g. csr_matrix.toarray for dense or csr_matrix for sparse)
dtype
numpy dtype applied to adata.X (e.g. 'float32' or 'int64')
small
False (default) returns full data, True returns small subset of the data."""

adata = pbmc3k().copy()

if small:
adata = adata[:1000, :500]
sc.pp.filter_cells(adata, min_genes=1)
np.random.seed(42)
adata.obs['batch'] = np.random.randint(0, 3, size=adata.shape[0])
sc.pp.filter_genes(adata, min_cells=1)
adata.X = sparsity_func(adata.X.astype(dtype))
return adata


def _check_check_values_warnings(function, adata, expected_warning, kwargs={}):
'''Runs `function` on `adata` with provided arguments `kwargs` twice: once with `check_values=True` and once with `check_values=False`. Checks that the `expected_warning` is only raised whtn `check_values=True`.'''
"""
Runs `function` on `adata` with provided arguments `kwargs` twice:
once with `check_values=True` and once with `check_values=False`.
Checks that the `expected_warning` is only raised whtn `check_values=True`.
"""

# expecting 0 no-int warnings
with warnings.catch_warnings(record=True) as record:
Expand Down
69 changes: 69 additions & 0 deletions scanpy/testing/_helpers/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Functions returning copies of datasets as cheaply as possible,
i.e. without having to hit the disk or (in case of ``_pbmc3k_normalized``) recomputing normalization.
"""

from __future__ import annotations

try:
from functools import cache
except ImportError: # Python < 3.9
from functools import lru_cache

def cache(func):
return lru_cache(maxsize=None)(func)


from anndata import AnnData
import scanpy as sc


# Functions returning the same objects (easy to misuse)


_pbmc3k = cache(sc.datasets.pbmc3k)
_pbmc3k_processed = cache(sc.datasets.pbmc3k_processed)
_pbmc68k_reduced = cache(sc.datasets.pbmc68k_reduced)
_krumsiek11 = cache(sc.datasets.krumsiek11)
_paul15 = cache(sc.datasets.paul15)


# Functions returning copies


def pbmc3k() -> AnnData:
return _pbmc3k().copy()


def pbmc3k_processed() -> AnnData:
return _pbmc3k_processed().copy()


def pbmc68k_reduced() -> AnnData:
return _pbmc68k_reduced().copy()


def krumsiek11() -> AnnData:
return _krumsiek11().copy()


def paul15() -> AnnData:
return _paul15().copy()


# Derived datasets


@cache
def _pbmc3k_normalized() -> AnnData:
pbmc = pbmc3k()
pbmc.X = pbmc.X.astype("float64") # For better accuracy
sc.pp.filter_genes(pbmc, min_counts=1)
sc.pp.log1p(pbmc)
sc.pp.normalize_total(pbmc)
sc.pp.highly_variable_genes(pbmc)
return pbmc


def pbmc3k_normalized() -> AnnData:
return _pbmc3k_normalized().copy()
48 changes: 48 additions & 0 deletions scanpy/testing/_pytest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""A private pytest plugin"""
import pytest

from .fixtures import * # noqa: F403


# In case pytest-nunit is not installed, defines a dummy fixture
try:
import pytest_nunit
except ModuleNotFoundError:

@pytest.fixture
def add_nunit_attachment(request):
def noop(file, description):
pass

return noop

def pytest_addoption(parser):
add_internet_tests_option(parser)
parser.addini("nunit_attach_on", "Dummy nunit replacement", default="any")

else:

def pytest_addoption(parser):
add_internet_tests_option(parser)


def add_internet_tests_option(parser):
parser.addoption(
"--internet-tests",
action="store_true",
default=False,
help=(
"Run tests that retrieve stuff from the internet. "
"This increases test time."
),
)


def pytest_collection_modifyitems(config, items):
run_internet = config.getoption("--internet-tests")
skip_internet = pytest.mark.skip(reason="need --internet-tests option to run")
for item in items:
# All tests marked with `pytest.mark.internet` get skipped unless
# `--run-internet` passed
if not run_internet and ("internet" in item.keywords):
item.add_marker(skip_internet)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@
from scipy import sparse

from anndata.tests.helpers import asarray
from .data import (
_pbmc3ks_parametrized_session,
pbmc3k_parametrized,
pbmc3k_parametrized_small,
)


__all__ = [
'array_type',
'float_dtype',
'_pbmc3ks_parametrized_session',
'pbmc3k_parametrized',
'pbmc3k_parametrized_small',
]


@pytest.fixture(
Expand Down
Loading