Skip to content

Commit 909f749

Browse files
committed
✅ test: add tests & CI
Signed-off-by: Nathaniel Starkman <[email protected]>
1 parent 1d187c1 commit 909f749

File tree

6 files changed

+238
-1
lines changed

6 files changed

+238
-1
lines changed

.github/workflows/ci.yml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: CI
2+
3+
permissions:
4+
contents: read
5+
packages: read
6+
7+
on:
8+
workflow_dispatch:
9+
pull_request:
10+
push:
11+
branches:
12+
- main
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
env:
19+
# Many color libraries just need this to be set to any value, but at least
20+
# one distinguishes color depth, where "3" -> "256-bit color".
21+
FORCE_COLOR: 3
22+
23+
jobs:
24+
format:
25+
name: Format
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Install uv
31+
uses: astral-sh/setup-uv@v6
32+
with:
33+
enable-cache: true
34+
cache-dependency-glob: "uv.lock"
35+
36+
- name: "Set up Python"
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version-file: ".python-version"
40+
41+
- name: Install the project
42+
run: uv sync --group test
43+
44+
- name: Run lefthook hooks
45+
run: uv run lefthook run
46+
47+
checks:
48+
name: Check Python ${{ matrix.python-version }} on ${{ matrix.runs-on }}
49+
runs-on: ${{ matrix.runs-on }}
50+
needs: [format]
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
python-version: ["3.11", "3.12", "3.13"]
55+
runs-on: [ubuntu-latest, macos-latest, windows-latest]
56+
57+
# TODO: check when pypy3.11 is available
58+
# include:
59+
# - python-version: pypy-3.11
60+
# runs-on: ubuntu-latest
61+
62+
steps:
63+
- uses: actions/checkout@v4
64+
65+
- name: Install uv
66+
uses: astral-sh/setup-uv@v6
67+
with:
68+
enable-cache: true
69+
cache-dependency-glob: "uv.lock"
70+
71+
- name: Set up Python ${{ matrix.python-version }}
72+
run: uv python install ${{ matrix.python-version }}
73+
74+
- name: Install the project
75+
run: uv sync --group test
76+
77+
- name: Test package
78+
run: >-
79+
uv run pytest src docs tests -ra --cov --cov-report=xml
80+
--cov-report=term --durations=20
81+
82+
- name: Upload coverage report
83+
uses: codecov/[email protected]
84+
with:
85+
token: ${{ secrets.CODECOV_TOKEN }}
86+
87+
check_oldest:
88+
name: Check Oldest Dependencies
89+
runs-on: ${{ matrix.runs-on }}
90+
needs: [format]
91+
strategy:
92+
fail-fast: false
93+
matrix:
94+
python-version: ["3.11"]
95+
runs-on: [ubuntu-latest]
96+
97+
steps:
98+
- uses: actions/checkout@v4
99+
100+
- name: Install uv
101+
uses: astral-sh/setup-uv@v6
102+
with:
103+
enable-cache: true
104+
cache-dependency-glob: "uv.lock"
105+
106+
- name: Set up Python ${{ matrix.python-version }}
107+
run: uv python install ${{ matrix.python-version }}
108+
109+
- name: Install the project
110+
run: uv sync --extra all --group test-all --resolution lowest-direct
111+
112+
- name: Test package
113+
run: >-
114+
uv run pytest src docs tests -ra --cov --cov-report=xml
115+
--cov-report=term --durations=20 --mpl
116+
117+
- name: Upload coverage report
118+
uses: codecov/[email protected]
119+
with:
120+
token: ${{ secrets.CODECOV_TOKEN }}

lefthook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ pre-commit:
1313
run: uv run ruff format {staged_files}
1414
- name: mypy
1515
glob: "*.py"
16-
run: uv run mypy {staged_files}
16+
run: uv run --group mypy mypy {staged_files}

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"pytest-github-actions-annotate-failures>=0.3.0",
5252
"sybil>=8.0.0",
5353
]
54+
mypy = [
55+
"mypy>=1.13.0"
56+
]
5457

5558

5659
[tool.hatch]
@@ -74,6 +77,7 @@ version_tuple = {version_tuple!r}
7477
[tool.mypy]
7578
files = ["src", "tests"]
7679
python_version = "3.10"
80+
mypy_path = "src"
7781

7882
strict = true
7983
disallow_incomplete_defs = true
@@ -90,6 +94,10 @@ version_tuple = {version_tuple!r}
9094
module = "sybil.*"
9195
ignore_missing_imports = true
9296

97+
[[tool.mypy.overrides]]
98+
module = "tests.*"
99+
disallow_untyped_defs = false
100+
93101

94102
[tool.pytest.ini_options]
95103
addopts = [
@@ -130,6 +138,9 @@ version_tuple = {version_tuple!r}
130138
"ISC001", # Conflicts with formatter
131139
]
132140

141+
[tool.ruff.lint.per-file-ignores]
142+
"tests/*.py" = ["ANN201", "S101"]
143+
133144
[tool.ruff.lint.flake8-import-conventions]
134145
banned-from = ["array_api_typing"]
135146

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests."""

tests/test_namespace.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Tests for the HasArrayNamespace protocol."""
2+
3+
from types import SimpleNamespace
4+
from typing import Protocol, runtime_checkable
5+
6+
import array_api_typing as xpt
7+
8+
9+
@runtime_checkable
10+
class CheckableHasArrayNamespace(xpt.HasArrayNamespace, Protocol): # type: ignore[misc]
11+
"""Runtime checkable version of HasArrayNamespace."""
12+
13+
14+
class GoodArray:
15+
"""Example class that implements the HasArrayNamespace protocol."""
16+
17+
def __array_namespace__(self) -> object: # noqa: PLW3201
18+
return SimpleNamespace()
19+
20+
21+
class BadArray:
22+
"""Example class that does not implement the HasArrayNamespace protocol."""
23+
24+
25+
def test_has_namespace_class():
26+
"""Test that GoodArray is a subclass of HasArrayNamespace."""
27+
assert issubclass(GoodArray, CheckableHasArrayNamespace)
28+
29+
30+
def test_has_namespace_instance():
31+
"""Test that an instance of GoodArray is recognized as HasArrayNamespace."""
32+
x = GoodArray()
33+
assert isinstance(x, CheckableHasArrayNamespace)
34+
35+
36+
def test_not_has_namespace_class():
37+
"""Test that BadArray is not a subclass of HasArrayNamespace."""
38+
assert not issubclass(BadArray, CheckableHasArrayNamespace)
39+
40+
41+
def test_not_has_namespace_instance():
42+
"""Test that an instance of BadArray is not recognized as HasArrayNamespace."""
43+
y = BadArray()
44+
assert not isinstance(y, CheckableHasArrayNamespace)

uv.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)