pytest-gpu-proof 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/.github/workflows/ci.yml +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/CHANGELOG.md +10 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/CONTRIBUTING.md +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/PKG-INFO +6 -4
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/README.md +2 -2
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/architecture.md +9 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/index.md +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/pyproject.toml +4 -2
- pytest_gpu_proof-0.5.0/requirements.txt +3 -0
- pytest_gpu_proof-0.5.0/src/pytest_gpu_proof/__init__.py +1 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/config.py +4 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/merge.py +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/plugin.py +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/receipt.py +15 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/verify.py +4 -4
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/conftest.py +13 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_cli.py +23 -4
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_config.py +20 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_merge.py +22 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_receipt.py +35 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_sharding.py +1 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_verifier.py +3 -3
- pytest_gpu_proof-0.4.1/requirements.txt +0 -2
- pytest_gpu_proof-0.4.1/src/pytest_gpu_proof/__init__.py +0 -1
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/.github/workflows/docs.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/.github/workflows/publish-testpypi.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/.github/workflows/publish.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/.gitignore +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/AGENTS.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/CLAUDE.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/LICENSE +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/RELEASING.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/ROADMAP.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/SECURITY.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/ci_gpu_mode.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/landscape.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/local_mode.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/policy.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/quickstart.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/security_model.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/docs/sharding.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/Makefile +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/minimal_python_only/conftest.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/minimal_python_only/test_minimal.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/install.sh +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/mkdocs.yml +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/requirements-dev.txt +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/__main__.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/cli.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/compare.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/fingerprint.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/gitutils.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/__init__.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/base.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/ed25519.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_compare.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_fingerprint.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_gitutils.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_plugin_capture.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/tests/test_signing.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/README.md +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/build_site.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/check_site.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/favicon.svg +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/index.html +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/main.js +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/overrides/main.html +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/provenance.json +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/requirements.txt +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/style.css +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/test_site.py +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/workflow-mobile.svg +0 -0
- {pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/website/workflow.svg +0 -0
|
@@ -4,6 +4,16 @@ All notable changes to pytest-gpu-proof are documented here. The format is
|
|
|
4
4
|
based on [Keep a Changelog](https://keepachangelog.com/); versions follow
|
|
5
5
|
[SemVer](https://semver.org/) (pre-1.0: minor bumps may break).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] — 2026-10-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Python 3.10 support with a conditional `tomli` dependency and a CI test lane.
|
|
12
|
+
- Signed `environment.nvcc_version` records the stripped output of
|
|
13
|
+
`nvcc --version` from PATH, or null when unavailable. Fresh shard merges
|
|
14
|
+
require matching compiler metadata; existing receipts without the field
|
|
15
|
+
remain supported.
|
|
16
|
+
|
|
7
17
|
## [0.4.1] — 2026-10-12
|
|
8
18
|
|
|
9
19
|
### Fixed
|
|
@@ -19,7 +19,7 @@ python -m build
|
|
|
19
19
|
python -m twine check --strict dist/*
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
CI runs the test suite on Python 3.11, 3.12, and 3.13, independently enforces
|
|
22
|
+
CI runs the test suite on Python 3.10, 3.11, 3.12, and 3.13, independently enforces
|
|
23
23
|
100% line and branch coverage, builds docs strictly, and smoke-tests the wheel.
|
|
24
24
|
|
|
25
25
|
## Change expectations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-gpu-proof
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys
|
|
5
5
|
Project-URL: Homepage, https://a2r-lab.org/pytest-gpu-proof/
|
|
6
6
|
Project-URL: Documentation, https://a2r-lab.org/pytest-gpu-proof/docs/
|
|
@@ -16,14 +16,16 @@ Classifier: Intended Audience :: Developers
|
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
23
|
Classifier: Topic :: Security :: Cryptography
|
|
23
24
|
Classifier: Topic :: Software Development :: Testing
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
25
26
|
Requires-Dist: cryptography>=41.0
|
|
26
27
|
Requires-Dist: pytest>=7.0
|
|
28
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
27
29
|
Provides-Extra: dev
|
|
28
30
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
29
31
|
Requires-Dist: coverage[toml]>=7.6; extra == 'dev'
|
|
@@ -90,7 +92,7 @@ cd pytest-gpu-proof
|
|
|
90
92
|
python -m pip install -e ".[dev]"
|
|
91
93
|
```
|
|
92
94
|
|
|
93
|
-
Requirements: Python 3.
|
|
95
|
+
Requirements: Python 3.10+, pytest 7+, and `cryptography` 41+.
|
|
94
96
|
|
|
95
97
|
## Quick start
|
|
96
98
|
|
|
@@ -344,7 +346,7 @@ python -m build
|
|
|
344
346
|
python -m twine check --strict dist/*
|
|
345
347
|
```
|
|
346
348
|
|
|
347
|
-
CI tests Python 3.
|
|
349
|
+
CI tests Python 3.10–3.13 and enforces 100% line and branch coverage. See
|
|
348
350
|
[CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
|
|
349
351
|
[RELEASING.md](RELEASING.md).
|
|
350
352
|
|
|
@@ -52,7 +52,7 @@ cd pytest-gpu-proof
|
|
|
52
52
|
python -m pip install -e ".[dev]"
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Requirements: Python 3.
|
|
55
|
+
Requirements: Python 3.10+, pytest 7+, and `cryptography` 41+.
|
|
56
56
|
|
|
57
57
|
## Quick start
|
|
58
58
|
|
|
@@ -306,7 +306,7 @@ python -m build
|
|
|
306
306
|
python -m twine check --strict dist/*
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
-
CI tests Python 3.
|
|
309
|
+
CI tests Python 3.10–3.13 and enforces 100% line and branch coverage. See
|
|
310
310
|
[CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
|
|
311
311
|
[RELEASING.md](RELEASING.md).
|
|
312
312
|
|
|
@@ -69,6 +69,15 @@ canonical = json.dumps(
|
|
|
69
69
|
Putting signer metadata inside this payload prevents identity or algorithm
|
|
70
70
|
substitution after signing.
|
|
71
71
|
|
|
72
|
+
The optional `environment.nvcc_version` field records the stripped stdout of
|
|
73
|
+
`nvcc --version` resolved through PATH. It is null if the executable is missing,
|
|
74
|
+
cannot run, times out after ten seconds, fails, or returns empty/undecodable output.
|
|
75
|
+
This is self-reported compiler availability, not evidence that this compiler built
|
|
76
|
+
the tested code. It is independent of GPU detection and requires no GPU.
|
|
77
|
+
Existing schema-3 receipts without the field still verify. Fresh shard merges
|
|
78
|
+
require matching values (missing and null are equivalent); carried shards retain
|
|
79
|
+
their original environment metadata.
|
|
80
|
+
|
|
72
81
|
## Fingerprint manifest
|
|
73
82
|
|
|
74
83
|
`sha256-manifest-v2` enumerates stage-0 Git index entries. Regular files bind
|
|
@@ -36,5 +36,5 @@ or exact SSH key fingerprints.
|
|
|
36
36
|
- [Architecture](architecture.md): schema and data flow.
|
|
37
37
|
- [Security model](security_model.md): exact guarantees and limits.
|
|
38
38
|
|
|
39
|
-
Requirements: Python 3.
|
|
39
|
+
Requirements: Python 3.10+, pytest 7+, and an SSH private-key file whose public
|
|
40
40
|
key is registered on GitHub.
|
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-gpu-proof"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
12
|
keywords = ["pytest", "gpu", "cuda", "testing", "signing", "attestation"]
|
|
13
13
|
classifiers = [
|
|
@@ -17,6 +17,7 @@ classifiers = [
|
|
|
17
17
|
"License :: OSI Approved :: MIT License",
|
|
18
18
|
"Operating System :: OS Independent",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
20
21
|
"Programming Language :: Python :: 3.11",
|
|
21
22
|
"Programming Language :: Python :: 3.12",
|
|
22
23
|
"Programming Language :: Python :: 3.13",
|
|
@@ -26,6 +27,7 @@ classifiers = [
|
|
|
26
27
|
dependencies = [
|
|
27
28
|
"pytest>=7.0",
|
|
28
29
|
"cryptography>=41.0",
|
|
30
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
29
31
|
]
|
|
30
32
|
|
|
31
33
|
[project.urls]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -67,7 +67,7 @@ def merge_payloads(receipts: List[dict], sources: List[str]) -> dict:
|
|
|
67
67
|
"repo.commit_sha")
|
|
68
68
|
_require_identical(receipts, sources, lambda r: r.get("fingerprint"), "fingerprint")
|
|
69
69
|
_require_identical(receipts, sources, lambda r: r.get("mode"), "mode")
|
|
70
|
-
for key in ("python_version", "platform", "pytest_version", "plugin_version"):
|
|
70
|
+
for key in ("python_version", "platform", "pytest_version", "plugin_version", "nvcc_version"):
|
|
71
71
|
_require_identical(receipts, sources,
|
|
72
72
|
lambda r, k=key: r.get("environment", {}).get(k),
|
|
73
73
|
f"environment.{key}")
|
|
@@ -17,7 +17,7 @@ from .config import GpuProofConfig, load_config
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def _utcnow() -> str:
|
|
20
|
-
return datetime.datetime.now(datetime.
|
|
20
|
+
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _has_gpu() -> bool:
|
|
@@ -25,7 +25,7 @@ from .gitutils import (
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def _utcnow() -> str:
|
|
28
|
-
return datetime.datetime.now(datetime.
|
|
28
|
+
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _gpu_info_from_torch() -> Optional[Dict[str, Any]]:
|
|
@@ -92,6 +92,19 @@ def _gpu_info(include_uuid: bool = True) -> Optional[Dict[str, Any]]:
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def _nvcc_version() -> Optional[str]:
|
|
96
|
+
"""Record the compiler on PATH, independently of GPU availability."""
|
|
97
|
+
try:
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
["nvcc", "--version"], capture_output=True, text=True, timeout=10,
|
|
100
|
+
)
|
|
101
|
+
except (OSError, subprocess.TimeoutExpired, UnicodeError):
|
|
102
|
+
return None
|
|
103
|
+
if result.returncode != 0:
|
|
104
|
+
return None
|
|
105
|
+
return result.stdout.strip() or None
|
|
106
|
+
|
|
107
|
+
|
|
95
108
|
def _env_info(record_gpu_uuid: bool = True) -> Dict[str, Any]:
|
|
96
109
|
import pytest
|
|
97
110
|
|
|
@@ -102,6 +115,7 @@ def _env_info(record_gpu_uuid: bool = True) -> Dict[str, Any]:
|
|
|
102
115
|
"platform": platform.platform(),
|
|
103
116
|
"pytest_version": pytest.__version__,
|
|
104
117
|
"plugin_version": __version__,
|
|
118
|
+
"nvcc_version": _nvcc_version(),
|
|
105
119
|
"gpu_info": _gpu_info(include_uuid=record_gpu_uuid),
|
|
106
120
|
}
|
|
107
121
|
|
|
@@ -179,7 +179,7 @@ def _parse_time(value, field: str) -> datetime.datetime:
|
|
|
179
179
|
raise VerificationError(f"{field} is missing or is not a UTC timestamp")
|
|
180
180
|
try:
|
|
181
181
|
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace(
|
|
182
|
-
tzinfo=datetime.
|
|
182
|
+
tzinfo=datetime.timezone.utc
|
|
183
183
|
)
|
|
184
184
|
except ValueError as exc:
|
|
185
185
|
raise VerificationError(f"{field} is not a valid UTC timestamp: {value!r}") from exc
|
|
@@ -498,7 +498,7 @@ def _verify(
|
|
|
498
498
|
raise VerificationError("repository policy requires recorded GPU information")
|
|
499
499
|
|
|
500
500
|
ended = _parse_time(session.get("ended_at"), "session.ended_at")
|
|
501
|
-
age = datetime.datetime.now(datetime.
|
|
501
|
+
age = datetime.datetime.now(datetime.timezone.utc) - ended
|
|
502
502
|
if age < datetime.timedelta(minutes=-5):
|
|
503
503
|
raise VerificationError("receipt timestamp is in the future")
|
|
504
504
|
if age > datetime.timedelta(days=max_days):
|
|
@@ -508,7 +508,7 @@ def _verify(
|
|
|
508
508
|
committed = get_commit_time(stored_sha, root)
|
|
509
509
|
if committed is not None:
|
|
510
510
|
started = _parse_time(session.get("started_at"), "session.started_at")
|
|
511
|
-
commit_dt = datetime.datetime.fromtimestamp(committed, datetime.
|
|
511
|
+
commit_dt = datetime.datetime.fromtimestamp(committed, datetime.timezone.utc)
|
|
512
512
|
if started < commit_dt - datetime.timedelta(minutes=5):
|
|
513
513
|
raise VerificationError(
|
|
514
514
|
"receipt session started before its commit existed "
|
|
@@ -577,7 +577,7 @@ def _verify_shards(receipt: dict, root: str, policy: dict, max_days: int = 30) -
|
|
|
577
577
|
raise VerificationError(
|
|
578
578
|
f"carried shard {name!r} was recorded with a dirty working tree")
|
|
579
579
|
original = _parse_time(carried.get("original_ended_at"), f"shard {name}.original_ended_at")
|
|
580
|
-
age = datetime.datetime.now(datetime.
|
|
580
|
+
age = datetime.datetime.now(datetime.timezone.utc) - original
|
|
581
581
|
# A carried shard's original run is never allowed to be older than
|
|
582
582
|
# the receipt itself may be, unless policy says so explicitly.
|
|
583
583
|
limit = policy.get("carried_max_age_days", min(30, max_days))
|
|
@@ -89,3 +89,16 @@ def tmp_git_repo(tmp_path):
|
|
|
89
89
|
"*.json\nid_*\nexpected_skips.txt\npyproject.toml\n"
|
|
90
90
|
)
|
|
91
91
|
return tmp_path
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.fixture(autouse=True)
|
|
95
|
+
def _no_host_nvcc(monkeypatch):
|
|
96
|
+
"""Compiler metadata tests override this deterministic default explicitly."""
|
|
97
|
+
run = subprocess.run
|
|
98
|
+
|
|
99
|
+
def without_host_nvcc(args, *positional, **kwargs):
|
|
100
|
+
if args == ["nvcc", "--version"]:
|
|
101
|
+
return subprocess.CompletedProcess(args, 0, stdout="test nvcc version\n", stderr="")
|
|
102
|
+
return run(args, *positional, **kwargs)
|
|
103
|
+
|
|
104
|
+
monkeypatch.setattr(subprocess, "run", without_host_nvcc)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import subprocess
|
|
2
|
+
from importlib.util import spec_from_file_location
|
|
3
|
+
from pathlib import Path
|
|
2
4
|
import sys
|
|
3
5
|
from types import SimpleNamespace
|
|
4
6
|
|
|
@@ -69,14 +71,31 @@ def test_merge_cli_success_and_error(monkeypatch, capsys):
|
|
|
69
71
|
def test_module_entrypoint(monkeypatch):
|
|
70
72
|
monkeypatch.setattr("pytest_gpu_proof.cli.main", lambda: (_ for _ in ()).throw(SystemExit(7)))
|
|
71
73
|
with pytest.raises(SystemExit) as exc:
|
|
72
|
-
|
|
74
|
+
path = Path(cli.__file__).with_name("__main__.py")
|
|
75
|
+
exec(compile(path.read_text(), str(path), "exec"),
|
|
76
|
+
{"__name__": "__main__", "__package__": "pytest_gpu_proof",
|
|
77
|
+
"__file__": str(path),
|
|
78
|
+
"__spec__": spec_from_file_location(f"pytest_gpu_proof.{path.stem}", path)})
|
|
73
79
|
assert exc.value.code == 7
|
|
74
80
|
|
|
75
81
|
|
|
76
|
-
@pytest.mark.filterwarnings("ignore:.*found in sys.modules.*:RuntimeWarning")
|
|
77
82
|
def test_cli_file_entrypoint(monkeypatch):
|
|
78
83
|
monkeypatch.setattr("pytest_gpu_proof.verify.verify_receipt", lambda **kwargs: True)
|
|
79
84
|
monkeypatch.setattr(sys, "argv", ["gpu-proof", "verify", "--receipt", "r.json"])
|
|
80
85
|
with pytest.raises(SystemExit) as exc:
|
|
81
|
-
|
|
86
|
+
path = Path(cli.__file__)
|
|
87
|
+
exec(compile(path.read_text(), str(path), "exec"),
|
|
88
|
+
{"__name__": "__main__", "__package__": "pytest_gpu_proof",
|
|
89
|
+
"__file__": str(path),
|
|
90
|
+
"__spec__": spec_from_file_location(f"pytest_gpu_proof.{path.stem}", path)})
|
|
82
91
|
assert exc.value.code == 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.parametrize("module", ["pytest_gpu_proof", "pytest_gpu_proof.cli"])
|
|
95
|
+
def test_module_cli_in_fresh_interpreter(module):
|
|
96
|
+
# Real -m invocation has no pytest assertion-rewriting loader. The in-process
|
|
97
|
+
# source-execution tests above retain coverage and mocks for exit/dispatch behavior.
|
|
98
|
+
result = subprocess.run([sys.executable, "-m", module, "--help"],
|
|
99
|
+
capture_output=True, text=True, timeout=10)
|
|
100
|
+
assert result.returncode == 0, result.stderr
|
|
101
|
+
assert "verify signed GPU test receipts" in result.stdout
|
|
@@ -121,3 +121,23 @@ def test_record_gpu_uuid_from_toml(tmp_path):
|
|
|
121
121
|
)
|
|
122
122
|
)
|
|
123
123
|
assert config.record_gpu_uuid is False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_tomli_fallback(monkeypatch, tmp_path):
|
|
127
|
+
import builtins
|
|
128
|
+
import runpy
|
|
129
|
+
import pytest_gpu_proof.config as config
|
|
130
|
+
|
|
131
|
+
original_import = builtins.__import__
|
|
132
|
+
|
|
133
|
+
def without_tomllib(name, *args, **kwargs):
|
|
134
|
+
if name == "tomllib":
|
|
135
|
+
raise ModuleNotFoundError("No module named 'tomllib'")
|
|
136
|
+
if name == "tomli":
|
|
137
|
+
return config.tomllib
|
|
138
|
+
return original_import(name, *args, **kwargs)
|
|
139
|
+
|
|
140
|
+
monkeypatch.setattr(builtins, "__import__", without_tomllib)
|
|
141
|
+
namespace = runpy.run_path(config.__file__)
|
|
142
|
+
(tmp_path / "pyproject.toml").write_text('[tool.gpu_proof]\nmode = "local"\n')
|
|
143
|
+
assert namespace["load_toml_defaults"](tmp_path) == {"mode": "local"}
|
|
@@ -30,7 +30,7 @@ def signer_with_key(tmp_path, ed25519_keypair):
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _utcstamp(minutes_ago: int = 0) -> str:
|
|
33
|
-
ts = datetime.datetime.now(datetime.
|
|
33
|
+
ts = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=minutes_ago)
|
|
34
34
|
return ts.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
35
35
|
|
|
36
36
|
|
|
@@ -241,3 +241,24 @@ def test_schema1_merge_has_no_schema3_session_fields():
|
|
|
241
241
|
merged = merge_payloads([base], ["one.json"])
|
|
242
242
|
assert "outcome" not in merged["session"]
|
|
243
243
|
assert "shards" not in merged
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@pytest.mark.parametrize("first, second", [("V13.2", "V12.8"), ("V13.2", None)])
|
|
247
|
+
def test_refuses_nvcc_mismatch(tmp_path, tmp_git_repo, signer_with_key, first, second):
|
|
248
|
+
signer, _, _ = signer_with_key
|
|
249
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")],
|
|
250
|
+
mutate=lambda p: p["environment"].__setitem__("nvcc_version", first))
|
|
251
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")],
|
|
252
|
+
mutate=lambda p: p["environment"].__setitem__("nvcc_version", second))
|
|
253
|
+
with pytest.raises(MergeError, match="environment.nvcc_version"):
|
|
254
|
+
merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())], ["a", "b"])
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_merge_old_receipt_without_nvcc(tmp_path, tmp_git_repo, signer_with_key):
|
|
258
|
+
signer, _, _ = signer_with_key
|
|
259
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")],
|
|
260
|
+
mutate=lambda p: p["environment"].pop("nvcc_version", None))
|
|
261
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")],
|
|
262
|
+
mutate=lambda p: p["environment"].__setitem__("nvcc_version", None))
|
|
263
|
+
merged = merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())], ["a", "b"])
|
|
264
|
+
assert len(merged["tests"]) == 2
|
|
@@ -339,3 +339,38 @@ def test_gpu_info_falls_back_to_torch_on_nvidia_smi_failure(monkeypatch):
|
|
|
339
339
|
lambda *a, **k: types.SimpleNamespace(returncode=1, stdout=""))
|
|
340
340
|
monkeypatch.setitem(sys.modules, "torch", None)
|
|
341
341
|
assert receipt_mod._gpu_info() is None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@pytest.mark.parametrize("code, output, expected", [
|
|
345
|
+
(0, "nvcc: NVIDIA (R) Cuda compiler driver\nCuda compilation tools, release 13.2, V13.2.51\n",
|
|
346
|
+
"nvcc: NVIDIA (R) Cuda compiler driver\nCuda compilation tools, release 13.2, V13.2.51"),
|
|
347
|
+
(0, " \n", None),
|
|
348
|
+
(1, "partial output", None),
|
|
349
|
+
])
|
|
350
|
+
def test_nvcc_version(monkeypatch, code, output, expected):
|
|
351
|
+
import subprocess
|
|
352
|
+
from pytest_gpu_proof.receipt import _nvcc_version
|
|
353
|
+
|
|
354
|
+
def run(args, **kwargs):
|
|
355
|
+
assert args == ["nvcc", "--version"]
|
|
356
|
+
assert kwargs == {"capture_output": True, "text": True, "timeout": 10}
|
|
357
|
+
return subprocess.CompletedProcess(args, code, stdout=output, stderr="ignored")
|
|
358
|
+
|
|
359
|
+
monkeypatch.setattr("pytest_gpu_proof.receipt.subprocess.run", run)
|
|
360
|
+
assert _nvcc_version() == expected
|
|
361
|
+
monkeypatch.setattr("pytest_gpu_proof.receipt._gpu_info", lambda **kw: None)
|
|
362
|
+
assert _env_info()["nvcc_version"] == expected
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@pytest.mark.parametrize("error", [
|
|
366
|
+
FileNotFoundError(), PermissionError(),
|
|
367
|
+
__import__("subprocess").TimeoutExpired("nvcc", 10), UnicodeError(),
|
|
368
|
+
])
|
|
369
|
+
def test_nvcc_unavailable(monkeypatch, error):
|
|
370
|
+
from pytest_gpu_proof.receipt import _nvcc_version
|
|
371
|
+
|
|
372
|
+
def run(*args, **kwargs):
|
|
373
|
+
raise error
|
|
374
|
+
|
|
375
|
+
monkeypatch.setattr("pytest_gpu_proof.receipt.subprocess.run", run)
|
|
376
|
+
assert _nvcc_version() is None
|
|
@@ -37,7 +37,7 @@ def signer_with_key(tmp_path, ed25519_keypair):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _utcstamp(days_ago: float = 0) -> str:
|
|
40
|
-
ts = datetime.datetime.now(datetime.
|
|
40
|
+
ts = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=days_ago)
|
|
41
41
|
return ts.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
42
42
|
|
|
43
43
|
|
|
@@ -38,7 +38,7 @@ def signer_with_key(tmp_path, keypair):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _utcstamp(days_ago: int = 0) -> str:
|
|
41
|
-
ts = datetime.datetime.now(datetime.
|
|
41
|
+
ts = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=days_ago)
|
|
42
42
|
return ts.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
43
43
|
|
|
44
44
|
|
|
@@ -273,7 +273,7 @@ def test_verify_fails_on_failed_test(tmp_path, tmp_git_repo, signer_with_key):
|
|
|
273
273
|
}
|
|
274
274
|
]
|
|
275
275
|
import datetime
|
|
276
|
-
now = datetime.datetime.now(datetime.
|
|
276
|
+
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
277
277
|
payload = build_receipt_payload(config, results, now, now)
|
|
278
278
|
receipt = finalize_receipt(payload, signer)
|
|
279
279
|
path = tmp_path / "gpu-proof.json"
|
|
@@ -571,7 +571,7 @@ def test_verifier_wrapper_reports_failure(tmp_path, tmp_git_repo, capsys):
|
|
|
571
571
|
|
|
572
572
|
def test_future_and_invalid_age_policy(tmp_path, tmp_git_repo, signer_with_key):
|
|
573
573
|
signer, public_key = signer_with_key
|
|
574
|
-
future = (datetime.datetime.now(datetime.
|
|
574
|
+
future = (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
575
575
|
path = _make_receipt(tmp_path, tmp_git_repo, signer, ended_at=future)
|
|
576
576
|
with _mock_github_keys(public_key), pytest.raises(VerificationError, match="future"):
|
|
577
577
|
_verify(str(path), None, str(tmp_git_repo), "testuser", None)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/bindings.cpp
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/bindings.cpp
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/minimal_python_only/test_minimal.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.4.1 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|