raptor-core 0.2.0__py3-none-any.whl
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.
- raptor/__init__.py +14 -0
- raptor/conformance/__init__.py +38 -0
- raptor/conformance/doc_includes.py +178 -0
- raptor/conformance/interop.py +393 -0
- raptor/protocols/__init__.py +24 -0
- raptor/protocols/backend.py +102 -0
- raptor/protocols/launcher.py +129 -0
- raptor/protocols/provider.py +38 -0
- raptor/py.typed +0 -0
- raptor/schema/__init__.py +88 -0
- raptor/schema/blocks.py +283 -0
- raptor/schema/dtypes.py +31 -0
- raptor/schema/manifest.py +270 -0
- raptor/version.py +13 -0
- raptor_core-0.2.0.dist-info/METADATA +328 -0
- raptor_core-0.2.0.dist-info/RECORD +20 -0
- raptor_core-0.2.0.dist-info/WHEEL +5 -0
- raptor_core-0.2.0.dist-info/licenses/LICENSE +202 -0
- raptor_core-0.2.0.dist-info/licenses/NOTICE +2 -0
- raptor_core-0.2.0.dist-info/top_level.txt +1 -0
raptor/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 Alessandro Masat
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""raptor — the protocol spine of the RAPTOR family.
|
|
5
|
+
|
|
6
|
+
Pure contracts; depends on nothing. Schemas live in :mod:`raptor.schema`;
|
|
7
|
+
typing.Protocols live in :mod:`raptor.protocols`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .version import __version__
|
|
13
|
+
|
|
14
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright 2026 Alessandro Masat
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""raptor.conformance — the interop certification declaration and its
|
|
5
|
+
reusable matrix-hygiene scanners. See :mod:`raptor.conformance.interop`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .interop import (
|
|
11
|
+
CERTIFIED,
|
|
12
|
+
ROADMAP,
|
|
13
|
+
ROW_KINDS,
|
|
14
|
+
ROWS,
|
|
15
|
+
UNIVERSE,
|
|
16
|
+
RowDecl,
|
|
17
|
+
assert_doc_rows_match,
|
|
18
|
+
assert_no_skip_tokens,
|
|
19
|
+
assert_rows_complete,
|
|
20
|
+
certified_framework,
|
|
21
|
+
register_row,
|
|
22
|
+
roadmap_framework,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"CERTIFIED",
|
|
27
|
+
"ROADMAP",
|
|
28
|
+
"ROW_KINDS",
|
|
29
|
+
"ROWS",
|
|
30
|
+
"UNIVERSE",
|
|
31
|
+
"RowDecl",
|
|
32
|
+
"assert_doc_rows_match",
|
|
33
|
+
"assert_no_skip_tokens",
|
|
34
|
+
"assert_rows_complete",
|
|
35
|
+
"certified_framework",
|
|
36
|
+
"register_row",
|
|
37
|
+
"roadmap_framework",
|
|
38
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Copyright 2026 Alessandro Masat
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""raptor.conformance.doc_includes — reusable Sphinx `include`/`literalinclude`
|
|
5
|
+
target checker.
|
|
6
|
+
|
|
7
|
+
**Why this exists.** `eagle/docs/conf.py` sets `suppress_warnings = [...,
|
|
8
|
+
"docutils", ...]` -- the category Sphinx uses for "Include file ... not
|
|
9
|
+
found" -- so a broken `literalinclude` target prints one warning line and the
|
|
10
|
+
build still exits 0. Several of eagle's targets once pointed at a directory
|
|
11
|
+
later extracted to a downstream package, and this shipped silently for the
|
|
12
|
+
length of the pass. Blanket `-W` is not an option (most surfaced eagle
|
|
13
|
+
warnings are pre-existing doxygen duplicate-ID noise under the SAME
|
|
14
|
+
"docutils" category, so the suppression cannot be narrowed without also
|
|
15
|
+
un-suppressing that noise). This module is a targeted,
|
|
16
|
+
non-Sphinx-warning-dependent check instead: a plain filesystem scan that
|
|
17
|
+
reproduces Sphinx's own path-resolution rules and asserts every
|
|
18
|
+
`include`/`literalinclude` target exists.
|
|
19
|
+
|
|
20
|
+
**Why it lives here, not downstream:** the logic was always path-free (it
|
|
21
|
+
takes `docs_root` as an argument and knows nothing about which repos exist
|
|
22
|
+
on disk or where they live), but it once shipped from a downstream sibling's
|
|
23
|
+
own suite. That placement broke silently the day that sibling's own suite
|
|
24
|
+
shape changed (a sibling-free venv-gate leg could no longer resolve the tool
|
|
25
|
+
at all, landing the file blocked-at-collection instead of running). The
|
|
26
|
+
claim "eagle's includes resolve" is eagle's; a claim gated only in a
|
|
27
|
+
downstream sibling's suite stops running the day that suite changes shape.
|
|
28
|
+
Moving the LOGIC here (raptor ships to every consumer; `include =
|
|
29
|
+
["raptor*"]`) lets each owner (`eagle/python/tests/test_doc_includes.py`,
|
|
30
|
+
a downstream package's own test) certify its OWN docs tree directly, with
|
|
31
|
+
raptor keeping zero sibling/repo knowledge.
|
|
32
|
+
|
|
33
|
+
**Resolution rules (reproduce Sphinx's `include`/`literalinclude` directives
|
|
34
|
+
exactly -- verified against the live eagle tree, not assumed):**
|
|
35
|
+
|
|
36
|
+
1. a target starting with "/" is relative to the docs SOURCE directory
|
|
37
|
+
(`docs_root`, the directory `conf.py` lives in) -- never the filesystem
|
|
38
|
+
root (a target like `/schemas/manifest-v1.schema.json` resolves to
|
|
39
|
+
`<docs_root>/schemas/manifest-v1.schema.json`, not `/schemas/...` on
|
|
40
|
+
disk);
|
|
41
|
+
2. otherwise the target is relative to the directory containing the
|
|
42
|
+
document that names it (the common case);
|
|
43
|
+
3. `_build/` is excluded -- it holds generated copies of the same source
|
|
44
|
+
pages (`_build/html/_sources/**`), so scanning it double-counts every
|
|
45
|
+
hit and would also flag the generated tree's own copies once a source
|
|
46
|
+
fix has already landed but the stale `_build/` has not been rebuilt.
|
|
47
|
+
|
|
48
|
+
Handles both reStructuredText directives (``.. literalinclude:: target``,
|
|
49
|
+
eagle's dialect) and MyST fenced directives (a code fence whose info string is
|
|
50
|
+
``{literalinclude} target`` -- three backticks followed by that info string,
|
|
51
|
+
the rest of the family's dialect) with the same scan, so the same call is
|
|
52
|
+
reusable across the family regardless of which markup a repo's docs use.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import re
|
|
58
|
+
import sys
|
|
59
|
+
from dataclasses import dataclass
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
|
|
62
|
+
#: reST: ".. include:: target" / ".. literalinclude:: target" (optionally
|
|
63
|
+
#: indented, e.g. nested inside a ".. dropdown::").
|
|
64
|
+
_RST_DIRECTIVE = re.compile(r"^\s*\.\.\s+(include|literalinclude)::\s+(\S+)\s*$")
|
|
65
|
+
|
|
66
|
+
#: MyST: "```{include} target" / "```{literalinclude} target" (the directive's
|
|
67
|
+
#: argument sits on the fence line itself).
|
|
68
|
+
_MYST_DIRECTIVE = re.compile(r"^\s*`{3,}\{(include|literalinclude)\}\s+(\S+)\s*$")
|
|
69
|
+
|
|
70
|
+
#: Source file suffixes scanned. `.rst` (docutils) and `.md` (MyST) are the
|
|
71
|
+
#: two dialects in use across the family's docs trees today.
|
|
72
|
+
SOURCE_SUFFIXES = (".rst", ".md")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class BrokenInclude:
|
|
77
|
+
"""One `include`/`literalinclude` directive whose target does not exist."""
|
|
78
|
+
|
|
79
|
+
doc: Path
|
|
80
|
+
line: int
|
|
81
|
+
directive: str
|
|
82
|
+
target: str
|
|
83
|
+
resolved: Path
|
|
84
|
+
|
|
85
|
+
def __str__(self) -> str:
|
|
86
|
+
return (
|
|
87
|
+
f"{self.doc}:{self.line}: .. {self.directive}:: {self.target} "
|
|
88
|
+
f"-> missing {self.resolved}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class DocsScanReport:
|
|
94
|
+
"""One `scan_docs` result: the root scanned, the TOTAL number of
|
|
95
|
+
`include`/`literalinclude` directives found (regardless of whether they
|
|
96
|
+
resolved), and the broken subset.
|
|
97
|
+
|
|
98
|
+
`n_directives` closes a vacuity trap: a scan pointed at an empty or
|
|
99
|
+
mis-rooted `docs_root` reports `broken == []` vacuously -- nothing was
|
|
100
|
+
found to break. A caller must assert `n_directives >= 1` alongside
|
|
101
|
+
`broken == []`, or an empty/wrong root passes silently.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
docs_root: Path
|
|
105
|
+
n_directives: int
|
|
106
|
+
broken: list[BrokenInclude]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def resolve_target(target: str, doc_path: Path, docs_root: Path) -> Path:
|
|
110
|
+
"""Resolve an `include`/`literalinclude` target using Sphinx's own rule.
|
|
111
|
+
|
|
112
|
+
``target`` starting with ``/`` is rooted at ``docs_root`` (the directory
|
|
113
|
+
``conf.py`` lives in), never the filesystem root. Otherwise it is
|
|
114
|
+
resolved relative to ``doc_path``'s own directory (the document that
|
|
115
|
+
names it), matching both docutils' `include` directive and Sphinx's
|
|
116
|
+
`literalinclude` extension of it.
|
|
117
|
+
"""
|
|
118
|
+
if target.startswith("/"):
|
|
119
|
+
return (docs_root / target[1:]).resolve()
|
|
120
|
+
return (doc_path.parent / target).resolve()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _iter_directives(text: str):
|
|
124
|
+
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
125
|
+
m = _RST_DIRECTIVE.match(line)
|
|
126
|
+
if m is None:
|
|
127
|
+
m = _MYST_DIRECTIVE.match(line)
|
|
128
|
+
if m is not None:
|
|
129
|
+
yield lineno, m.group(1), m.group(2)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def scan_docs(docs_root) -> DocsScanReport:
|
|
133
|
+
"""Scan every ``.rst``/``.md`` file under ``docs_root`` (excluding
|
|
134
|
+
``_build/``) for `include`/`literalinclude` directives, resolving each
|
|
135
|
+
with :func:`resolve_target`. One filesystem walk produces both the total
|
|
136
|
+
directive count and the broken subset (see :class:`DocsScanReport`).
|
|
137
|
+
"""
|
|
138
|
+
docs_root = Path(docs_root).resolve()
|
|
139
|
+
n_directives = 0
|
|
140
|
+
broken: list[BrokenInclude] = []
|
|
141
|
+
for suffix in SOURCE_SUFFIXES:
|
|
142
|
+
for doc_path in sorted(docs_root.rglob(f"*{suffix}")):
|
|
143
|
+
if "_build" in doc_path.relative_to(docs_root).parts:
|
|
144
|
+
continue
|
|
145
|
+
text = doc_path.read_text(encoding="utf-8")
|
|
146
|
+
for lineno, directive, target in _iter_directives(text):
|
|
147
|
+
n_directives += 1
|
|
148
|
+
resolved = resolve_target(target, doc_path, docs_root)
|
|
149
|
+
if not resolved.is_file():
|
|
150
|
+
broken.append(
|
|
151
|
+
BrokenInclude(doc_path, lineno, directive, target, resolved)
|
|
152
|
+
)
|
|
153
|
+
return DocsScanReport(docs_root, n_directives, broken)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def main(argv: list[str] | None = None) -> int:
|
|
157
|
+
"""CLI entry point: print the directive count and every broken include
|
|
158
|
+
found under each docs root given."""
|
|
159
|
+
argv = sys.argv[1:] if argv is None else argv
|
|
160
|
+
if not argv:
|
|
161
|
+
print(
|
|
162
|
+
"usage: python -m raptor.conformance.doc_includes <docs_root> "
|
|
163
|
+
"[<docs_root> ...]",
|
|
164
|
+
file=sys.stderr,
|
|
165
|
+
)
|
|
166
|
+
return 2
|
|
167
|
+
red = 0
|
|
168
|
+
for root in argv:
|
|
169
|
+
report = scan_docs(Path(root))
|
|
170
|
+
print(f"{root}: {report.n_directives} directive(s) scanned", file=sys.stderr)
|
|
171
|
+
for broken in report.broken:
|
|
172
|
+
red = 1
|
|
173
|
+
print(broken)
|
|
174
|
+
return red
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Copyright 2026 Alessandro Masat
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""The interop certification declaration and reusable matrix-hygiene scanners.
|
|
5
|
+
|
|
6
|
+
raptor is the framework-free contract owner: this module names every
|
|
7
|
+
framework the family will ever bridge to (:data:`UNIVERSE`), which subset is
|
|
8
|
+
certified today (:data:`CERTIFIED`) vs. declared-but-not-yet-built
|
|
9
|
+
(:data:`ROADMAP`), and the row-level ``ROWS`` catalogue, declared here in
|
|
10
|
+
full ahead of the packages that write the matrix tests — so that
|
|
11
|
+
:func:`assert_rows_complete` has a real gap to catch instead of declared and
|
|
12
|
+
collected always landing together. Framework objects NEVER enter raptor —
|
|
13
|
+
pointers and callables are passed in duck-typed; this module itself imports
|
|
14
|
+
no framework, ever (``find_spec`` never imports).
|
|
15
|
+
|
|
16
|
+
Pure-Python, numpy-free by design — raptor has zero hard dependencies.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import ast
|
|
22
|
+
import importlib.util
|
|
23
|
+
import re
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
#: every framework the family will ever bridge to, day-0 and roadmap together.
|
|
28
|
+
UNIVERSE = ("numpy", "cupy", "torch", "warp", "jax", "tensorflow")
|
|
29
|
+
|
|
30
|
+
#: frameworks with an executable, certified round-trip today.
|
|
31
|
+
CERTIFIED = ("numpy", "cupy", "torch", "warp")
|
|
32
|
+
|
|
33
|
+
#: frameworks declared absent-by-design — no row certifies them yet.
|
|
34
|
+
ROADMAP = ("jax", "tensorflow")
|
|
35
|
+
|
|
36
|
+
#: the vocabulary a :class:`RowDecl` may declare for how a row moves data.
|
|
37
|
+
#: ``ALIAS`` — the crossing shares the same underlying buffer, no copy (e.g.
|
|
38
|
+
#: DLPack zero-copy into another framework's tensor).
|
|
39
|
+
#: ``COPY`` — the crossing makes an independent copy of the data.
|
|
40
|
+
#: ``STREAM`` — the crossing's ordering relative to other work on the same
|
|
41
|
+
#: stream/queue (not whether data is shared or copied).
|
|
42
|
+
#: ``ENV`` — a precondition on the execution environment/state that gates
|
|
43
|
+
#: whether a crossing is legal (e.g. CUDA graph capture mode, device
|
|
44
|
+
#: placement, a layout self-check) — certifying a loud refusal under the
|
|
45
|
+
#: wrong state as much as acceptance under the right one.
|
|
46
|
+
#: ``INVARIANCE`` — the same plugin body produces equivalent results under
|
|
47
|
+
#: different execution structures (bit-exact for some structures, within a
|
|
48
|
+
#: tolerance band for others) — distinct from ALIAS/COPY (which certify a
|
|
49
|
+
#: framework crossing's pointer semantics) and from STREAM (ordering).
|
|
50
|
+
ROW_KINDS = frozenset({"ALIAS", "COPY", "STREAM", "ENV", "INVARIANCE"})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class RowDecl:
|
|
55
|
+
"""One declared interop-matrix row.
|
|
56
|
+
|
|
57
|
+
``framework`` must be a member of :data:`CERTIFIED`, OR ``None`` for a row
|
|
58
|
+
that bridges to no array framework at all — an execution-structure
|
|
59
|
+
invariant internal to eagle rather than a numpy/cupy/torch crossing.
|
|
60
|
+
Every prior row, even ``CP-CAPTURE-REJECT``/``T-DEVICE-FAILLOUD``, still
|
|
61
|
+
names the framework whose crossing it guards; widening ``framework`` to
|
|
62
|
+
``Optional[str]`` — rather than inventing a sixth pseudo-framework entry
|
|
63
|
+
in :data:`CERTIFIED`/:data:`UNIVERSE` — keeps that vocabulary honestly
|
|
64
|
+
scoped to actual array frameworks. ``owner_repo`` is the repo whose test
|
|
65
|
+
suite carries the executable assertion, so the matrix rows live with the
|
|
66
|
+
seam they certify; ``kind`` is one of :data:`ROW_KINDS`.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
framework: str | None
|
|
70
|
+
owner_repo: str
|
|
71
|
+
kind: str
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
if self.kind not in ROW_KINDS:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"RowDecl.kind={self.kind!r} not in {sorted(ROW_KINDS)}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
#: row_id -> RowDecl — the full row catalogue, declared here ahead of the
|
|
81
|
+
#: packages that write the tests. This is deliberate: if declaring and
|
|
82
|
+
#: collecting happened in the same later package, declared and collected
|
|
83
|
+
#: would always agree by construction and :func:`assert_rows_complete` could
|
|
84
|
+
#: never bite. Every row named elsewhere in the family exists below under its
|
|
85
|
+
#: catalogue ID.
|
|
86
|
+
ROWS: dict[str, RowDecl] = {
|
|
87
|
+
# numpy (host) rows.
|
|
88
|
+
# cupy — kernel-level rows and adapter/pipeline rows, split by owner.
|
|
89
|
+
# CP-STREAM-ORDER is a named cross-reference to STREAM-CUPY-PRODUCER-ORDER
|
|
90
|
+
# below, not a second row.
|
|
91
|
+
"CP-CAPTURE-REJECT": RowDecl("cupy", "eagle", "ENV"),
|
|
92
|
+
# torch — adapter+stream rows, kernel-level rows, and bridge+backend
|
|
93
|
+
# rows, split by owner.
|
|
94
|
+
"T-IN-CUDA-ALIAS": RowDecl("torch", "eagle", "ALIAS"),
|
|
95
|
+
"T-OUT-CUDA-ALIAS": RowDecl("torch", "eagle", "ALIAS"),
|
|
96
|
+
"T-IN-CPU-COPY": RowDecl("torch", "eagle", "COPY"),
|
|
97
|
+
# A row separate from T-IN-CPU-COPY immediately above: that row stays
|
|
98
|
+
# owner=eagle and keeps certifying eagle's own adapter crossing. This one
|
|
99
|
+
# certifies a different seam: a native host provider round-trips a
|
|
100
|
+
# conforming (float64, C-contiguous) torch CPU tensor by pointer-alias —
|
|
101
|
+
# no upload, no eagle, no cupy. The declaration here is authoritative, an
|
|
102
|
+
# explicit re-declaration rather than a quiet one.
|
|
103
|
+
# Note eagle's adapter ALSO copies float32 (``eagle/python/eagle/interop.py``);
|
|
104
|
+
# this row certifies the kernel-level crossing, per the grouping above.
|
|
105
|
+
"T-BRIDGE-COPY": RowDecl("torch", "learning_runtime", "COPY"),
|
|
106
|
+
"T-BACKEND-CPU-IN-ALIAS": RowDecl("torch", "learning_runtime", "ALIAS"),
|
|
107
|
+
# Renamed from "T-BACKEND-CUDA": kind=ALIAS is true for the INPUT crossing
|
|
108
|
+
# only — the learning runtime's bridge copies unconditionally on export
|
|
109
|
+
# (any live replay buffer is copied off), so no backend row can be an
|
|
110
|
+
# alias in the OUT direction — that copy is itself certified by
|
|
111
|
+
# T-BRIDGE-COPY. The unqualified name would have published as "the CUDA
|
|
112
|
+
# backend aliases", which is false for outputs; the ID now names its
|
|
113
|
+
# direction, matching its CPU sibling.
|
|
114
|
+
"T-BACKEND-CUDA-IN-ALIAS": RowDecl("torch", "learning_runtime", "ALIAS"),
|
|
115
|
+
"T-DEVICE-FAILLOUD": RowDecl("torch", "learning_runtime", "ENV"),
|
|
116
|
+
# stream rows — owner eagle, GPU-serial.
|
|
117
|
+
"STREAM-TORCH-PRODUCER-ORDER": RowDecl("torch", "eagle", "STREAM"),
|
|
118
|
+
"STREAM-CUPY-PRODUCER-ORDER": RowDecl("cupy", "eagle", "STREAM"),
|
|
119
|
+
"STREAM-TORCH-CONSUMER-ORDER": RowDecl("torch", "eagle", "STREAM"),
|
|
120
|
+
"STREAM-IDENTITY": RowDecl("torch", "eagle", "STREAM"),
|
|
121
|
+
# warp — NVIDIA Warp crosses through eagle's generic zero-copy buffer
|
|
122
|
+
# layer (eagle.interop.import_buffer), not a dedicated Adapter: any
|
|
123
|
+
# __dlpack__ + __dlpack_device__ producer already routes through
|
|
124
|
+
# eagle.interop._DlpackAdapter, so warp needed no new adapter code, only
|
|
125
|
+
# these rows. Warp speaks legacy (pre-1.0) DLPack, so its access flag is
|
|
126
|
+
# always "unknown" — that is part of WP-IN-CUDA-ALIAS, not a separate
|
|
127
|
+
# row (same bundling convention T-IN-CUDA-ALIAS already uses for its own
|
|
128
|
+
# three legs). It joins the other STREAM-* rows.
|
|
129
|
+
"WP-IN-CUDA-ALIAS": RowDecl("warp", "eagle", "ALIAS"),
|
|
130
|
+
"WP-OUT-CUDA-ALIAS": RowDecl("warp", "eagle", "ALIAS"),
|
|
131
|
+
"STREAM-WARP-PRODUCER-ORDER": RowDecl("warp", "eagle", "STREAM"),
|
|
132
|
+
# execution rows: the closed RowDecl framework/kind sets extended (above)
|
|
133
|
+
# so these four can be declared here, ahead of their eagle-owned consumer
|
|
134
|
+
# implementation — the same declare-before-build convention every other
|
|
135
|
+
# eagle-owned row above already uses (e.g. CP-CAPTURE-REJECT): raptor's
|
|
136
|
+
# own suite never calls ``assert_rows_complete("eagle")``, so a
|
|
137
|
+
# declared-but-uncollected eagle row is not a gap this repo's gate can
|
|
138
|
+
# see — that completeness gate is eagle's own, in its own suite. Row ids
|
|
139
|
+
# are given in eagle's own snake_case naming, not this file's
|
|
140
|
+
# UPPER-HYPHEN convention — kept as given rather than renamed to fit.
|
|
141
|
+
"execution_partition_identity": RowDecl(None, "eagle", "INVARIANCE"),
|
|
142
|
+
"execution_host_device_twin": RowDecl(None, "eagle", "INVARIANCE"),
|
|
143
|
+
"execution_illegal_placement_refused": RowDecl(None, "eagle", "ENV"),
|
|
144
|
+
"execution_layout_selfcheck_refused": RowDecl(None, "eagle", "ENV"),
|
|
145
|
+
# HAWK rows: rows that exist only because HAWK exists, declared
|
|
146
|
+
# owner_repo="hawk" — same declare-before-build convention as the
|
|
147
|
+
# execution_* rows above, ahead of HAWK's own consumer implementation.
|
|
148
|
+
# The numpy/cupy/torch crossing rows HAWK will own are declared when its
|
|
149
|
+
# consumer implementation lands, not here. framework=None for all
|
|
150
|
+
# five: none bridges to an array framework (see RowDecl's docstring);
|
|
151
|
+
# every check is deferred to its own consumer implementation. Row ids
|
|
152
|
+
# cite the row they carry in hawk's own documentation, which gives no
|
|
153
|
+
# snake_case name for them (contrast the execution_* rows' eagle-given
|
|
154
|
+
# names above) — this file's own UPPER-HYPHEN convention applies,
|
|
155
|
+
# prefixed HAWK- for the family.
|
|
156
|
+
"HAWK-DIFF-POSITIVE-ROUNDTRIP": RowDecl(None, "hawk", "INVARIANCE"),
|
|
157
|
+
"HAWK-EXEC-DEPLOY-LOADABLE": RowDecl(None, "hawk", "ENV"),
|
|
158
|
+
"HAWK-EXEC-REFERENCE-HOST": RowDecl(None, "hawk", "INVARIANCE"),
|
|
159
|
+
"HAWK-EXEC-REFERENCE-RANKPARTITION": RowDecl(None, "hawk", "INVARIANCE"),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def certified_framework(fw: str) -> None:
|
|
164
|
+
"""Assert ``fw`` (a name in :data:`CERTIFIED`) is importable here.
|
|
165
|
+
|
|
166
|
+
Never skips: an absent certified framework is a hard failure of the gate
|
|
167
|
+
environment, not a reason to skip the row (a pytest SKIP is RC=0 and
|
|
168
|
+
certifies nothing). ``pytest`` is imported here, not at module level, so
|
|
169
|
+
the rest of :mod:`raptor.conformance` stays importable without it.
|
|
170
|
+
"""
|
|
171
|
+
import pytest
|
|
172
|
+
|
|
173
|
+
if importlib.util.find_spec(fw) is None:
|
|
174
|
+
pytest.fail("certified framework absent from the gate environment")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def roadmap_framework(fw: str) -> None:
|
|
178
|
+
"""Assert ``fw`` (a name in :data:`ROADMAP`) is ABSENT here.
|
|
179
|
+
|
|
180
|
+
Certified-absent and roadmap-present are BOTH failures — the declaration
|
|
181
|
+
and reality must agree in both directions, or the matrix certifies
|
|
182
|
+
nothing.
|
|
183
|
+
"""
|
|
184
|
+
assert importlib.util.find_spec(fw) is None, (
|
|
185
|
+
"declared ROADMAP (absent-by-declaration) but INSTALLED — certify it "
|
|
186
|
+
"or remove it from the env"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
#: row IDs actually collected this pytest session (populated by
|
|
191
|
+
#: :func:`register_row` at collection time).
|
|
192
|
+
_COLLECTED_ROWS: set[str] = set()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def register_row(row_id: str):
|
|
196
|
+
"""Decorator marking a matrix test as covering declared row ``row_id``.
|
|
197
|
+
|
|
198
|
+
Raises :class:`KeyError` immediately if ``row_id`` is not in :data:`ROWS`
|
|
199
|
+
(a typo'd row id should fail loud at collection, not silently pass
|
|
200
|
+
:func:`assert_rows_complete`).
|
|
201
|
+
"""
|
|
202
|
+
if row_id not in ROWS:
|
|
203
|
+
raise KeyError(f"register_row: {row_id!r} is not a declared row in ROWS")
|
|
204
|
+
|
|
205
|
+
def _decorate(fn):
|
|
206
|
+
_COLLECTED_ROWS.add(row_id)
|
|
207
|
+
return fn
|
|
208
|
+
|
|
209
|
+
return _decorate
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def assert_rows_complete(owner_repo: str) -> None:
|
|
213
|
+
"""Assert every :data:`ROWS` entry owned by ``owner_repo`` was collected.
|
|
214
|
+
|
|
215
|
+
Declared vs. collected row IDs; any declared row with no collected test
|
|
216
|
+
is RED.
|
|
217
|
+
"""
|
|
218
|
+
declared = {rid for rid, decl in ROWS.items() if decl.owner_repo == owner_repo}
|
|
219
|
+
missing = declared - _COLLECTED_ROWS
|
|
220
|
+
assert not missing, (
|
|
221
|
+
f"rows declared for {owner_repo!r} but never collected: {sorted(missing)}"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
#: token names that must never appear in a matrix module — skip machinery is
|
|
226
|
+
#: banned from matrix modules, mechanically.
|
|
227
|
+
_SKIP_TOKEN_ATTRS = frozenset({"skip", "skipif", "importorskip"})
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def assert_no_skip_tokens(*module_paths) -> None:
|
|
231
|
+
"""Assert zero ``pytest.skip``/``skipif``/``importorskip`` tokens.
|
|
232
|
+
|
|
233
|
+
AST-based over each path in ``module_paths`` (a matrix module file):
|
|
234
|
+
catches direct calls (``pytest.skip(...)``, ``pytest.importorskip(...)``)
|
|
235
|
+
and decorator uses (``@pytest.mark.skip``/``@pytest.mark.skipif``) alike,
|
|
236
|
+
since decorators are ordinary ``Attribute``/``Call`` nodes in the AST.
|
|
237
|
+
Absence of a certified framework must be a FAILURE by construction, never
|
|
238
|
+
a silent skip.
|
|
239
|
+
"""
|
|
240
|
+
hits: list[str] = []
|
|
241
|
+
for raw_path in module_paths:
|
|
242
|
+
path = Path(raw_path)
|
|
243
|
+
tree = ast.parse(path.read_text(), filename=str(path))
|
|
244
|
+
for node in ast.walk(tree):
|
|
245
|
+
if isinstance(node, ast.Attribute) and node.attr in _SKIP_TOKEN_ATTRS:
|
|
246
|
+
hits.append(f"{path}:{node.lineno}: pytest.{node.attr}")
|
|
247
|
+
assert not hits, "banned skip tokens found in matrix modules:\n" + "\n".join(hits)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
#: markers ANY conftest in the family wires into ``pytest_collection_modifyitems``'s
|
|
251
|
+
#: skip-on-absence machinery — the union across every family member's own
|
|
252
|
+
#: availability markers (verified against each repo's own
|
|
253
|
+
#: ``conftest.py``/``pyproject.toml`` directly, not assumed). Checking for a
|
|
254
|
+
#: marker a given repo's conftest doesn't define is always a zero-hit, never
|
|
255
|
+
#: a false negative — the union is safe to use everywhere.
|
|
256
|
+
_AVAILABILITY_MARKERS = frozenset({"gpu", "torch", "torch_gpu", "jax_gpu", "tf_gpu"})
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _calls_register_row(tree: ast.AST) -> bool:
|
|
260
|
+
return any(
|
|
261
|
+
isinstance(node, ast.Name) and node.id == "register_row"
|
|
262
|
+
for node in ast.walk(tree)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def discover_matrix_modules(test_dir) -> list[Path]:
|
|
267
|
+
"""Every ``.py`` file under ``test_dir`` that calls :func:`register_row`
|
|
268
|
+
(AST-detected, not textually grepped) — the matrix modules a repo actually
|
|
269
|
+
ships, DISCOVERED rather than hand-passed.
|
|
270
|
+
|
|
271
|
+
A hand-passed module list is exactly how a matrix row can go unscanned by
|
|
272
|
+
accident — a row registered from a file its scanner's caller forgot to
|
|
273
|
+
name still counts as "collected" (``register_row`` fires at DECORATION
|
|
274
|
+
time, independent of whether pytest later skips the test), so
|
|
275
|
+
completeness and skip-freedom silently diverge. Discovery closes that gap
|
|
276
|
+
by construction: there is no second list to forget to update.
|
|
277
|
+
"""
|
|
278
|
+
return sorted(p for p in Path(test_dir).rglob("*.py") if _calls_register_row(
|
|
279
|
+
ast.parse(p.read_text(), filename=str(p))
|
|
280
|
+
))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _registered_function_defs(tree: ast.AST):
|
|
284
|
+
"""Yield ``(row_id, FunctionDef)`` for every function decorated
|
|
285
|
+
``@register_row("...")`` in ``tree`` — the exact, narrow locus a matrix
|
|
286
|
+
row's own hygiene must hold at (not the whole file: a file mixing matrix
|
|
287
|
+
rows with ordinary, legitimately-marker-gated tests must not have its
|
|
288
|
+
unrelated tests' markers flagged)."""
|
|
289
|
+
for node in ast.walk(tree):
|
|
290
|
+
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
291
|
+
continue
|
|
292
|
+
for dec in node.decorator_list:
|
|
293
|
+
if (
|
|
294
|
+
isinstance(dec, ast.Call)
|
|
295
|
+
and isinstance(dec.func, ast.Name)
|
|
296
|
+
and dec.func.id == "register_row"
|
|
297
|
+
):
|
|
298
|
+
row_id = (
|
|
299
|
+
dec.args[0].value
|
|
300
|
+
if dec.args and isinstance(dec.args[0], ast.Constant)
|
|
301
|
+
else "<unresolved>"
|
|
302
|
+
)
|
|
303
|
+
yield row_id, node
|
|
304
|
+
break
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _is_marker_decorator(dec: ast.expr, banned_markers: frozenset[str]) -> str | None:
|
|
308
|
+
"""Return the banned marker name ``dec`` applies (``@pytest.mark.<name>``,
|
|
309
|
+
bare or called), or ``None``."""
|
|
310
|
+
target = dec.func if isinstance(dec, ast.Call) else dec
|
|
311
|
+
if (
|
|
312
|
+
isinstance(target, ast.Attribute)
|
|
313
|
+
and target.attr in banned_markers
|
|
314
|
+
and isinstance(target.value, ast.Attribute)
|
|
315
|
+
and target.value.attr == "mark"
|
|
316
|
+
):
|
|
317
|
+
return target.attr
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def assert_no_skip_machinery(
|
|
322
|
+
test_dir, *, banned_markers: frozenset[str] = _AVAILABILITY_MARKERS
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Assert every DISCOVERED matrix row (any ``@register_row``-decorated
|
|
325
|
+
function in any ``.py`` under ``test_dir``) is free of skip machinery:
|
|
326
|
+
zero ``pytest.skip``/``skipif``/``importorskip`` tokens in its body, AND
|
|
327
|
+
zero conftest availability-marker decorators (``banned_markers``, default
|
|
328
|
+
the cross-repo union) on the registered function itself.
|
|
329
|
+
|
|
330
|
+
Scoped to the REGISTERED function, not the whole file (see
|
|
331
|
+
:func:`_registered_function_defs`) — a file may legitimately mix matrix
|
|
332
|
+
rows with ordinary marker-gated tests; only the declared rows must be
|
|
333
|
+
unable to skip. Discovery (:func:`discover_matrix_modules`) means there is
|
|
334
|
+
no hand-passed module list to fall out of sync with reality: that is
|
|
335
|
+
exactly how a row once skipped silently while
|
|
336
|
+
:func:`assert_rows_complete` still passed — ``register_row`` collects at
|
|
337
|
+
decoration time, not at execution.
|
|
338
|
+
"""
|
|
339
|
+
modules = discover_matrix_modules(test_dir)
|
|
340
|
+
assert modules, f"no register_row-bearing modules found under {test_dir}"
|
|
341
|
+
skip_hits: list[str] = []
|
|
342
|
+
marker_hits: list[str] = []
|
|
343
|
+
for path in modules:
|
|
344
|
+
tree = ast.parse(path.read_text(), filename=str(path))
|
|
345
|
+
for row_id, fn in _registered_function_defs(tree):
|
|
346
|
+
for dec in fn.decorator_list:
|
|
347
|
+
marker = _is_marker_decorator(dec, banned_markers)
|
|
348
|
+
if marker is not None:
|
|
349
|
+
marker_hits.append(
|
|
350
|
+
f"{path}:{fn.lineno}: row {row_id!r} carries "
|
|
351
|
+
f"@pytest.mark.{marker} (skip-capable)"
|
|
352
|
+
)
|
|
353
|
+
for sub in ast.walk(fn):
|
|
354
|
+
if isinstance(sub, ast.Attribute) and sub.attr in _SKIP_TOKEN_ATTRS:
|
|
355
|
+
skip_hits.append(
|
|
356
|
+
f"{path}:{sub.lineno}: row {row_id!r} uses pytest.{sub.attr}"
|
|
357
|
+
)
|
|
358
|
+
assert not skip_hits, "banned skip tokens found on registered rows:\n" + "\n".join(
|
|
359
|
+
skip_hits
|
|
360
|
+
)
|
|
361
|
+
assert not marker_hits, (
|
|
362
|
+
"banned availability-marker decorators found on registered rows:\n"
|
|
363
|
+
+ "\n".join(marker_hits)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def assert_doc_rows_match(
|
|
368
|
+
doc_path,
|
|
369
|
+
rows: dict[str, RowDecl],
|
|
370
|
+
*,
|
|
371
|
+
row_id_pattern: str = r"`([A-Za-z][A-Za-z0-9_.:/-]*)`",
|
|
372
|
+
) -> None:
|
|
373
|
+
"""Assert the doc's row IDs and ``rows``'s keys are the same set.
|
|
374
|
+
|
|
375
|
+
Mirrors ``test_roles_vocab.py``'s technique (``eagle/python/tests/
|
|
376
|
+
test_roles_vocab.py``): the document is read as raw TEXT and scanned with
|
|
377
|
+
a regex — no markdown parser, no import of anything the doc describes —
|
|
378
|
+
then compared by set equality against the declaration, in both
|
|
379
|
+
directions. ``row_id_pattern`` defaults to backtick-quoted identifiers
|
|
380
|
+
(the row-id convention a table of ``` `row_id` ``` cells would use);
|
|
381
|
+
override it if the shipped document's convention differs.
|
|
382
|
+
"""
|
|
383
|
+
text = Path(doc_path).read_text()
|
|
384
|
+
doc_ids = set(re.findall(row_id_pattern, text))
|
|
385
|
+
declared_ids = set(rows)
|
|
386
|
+
missing_from_doc = declared_ids - doc_ids
|
|
387
|
+
extra_in_doc = doc_ids - declared_ids
|
|
388
|
+
assert not missing_from_doc, (
|
|
389
|
+
f"rows declared but missing from {doc_path}: {sorted(missing_from_doc)}"
|
|
390
|
+
)
|
|
391
|
+
assert not extra_in_doc, (
|
|
392
|
+
f"{doc_path} references undeclared rows: {sorted(extra_in_doc)}"
|
|
393
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright 2026 Alessandro Masat
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""raptor.protocols — the family's typing.Protocols.
|
|
5
|
+
|
|
6
|
+
Pure structural contracts; zero hard deps; zero imports of any downstream
|
|
7
|
+
package.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .backend import CAPABILITIES, Backend, CapabilityError, Executable
|
|
13
|
+
from .launcher import KernelLauncher, Marshal
|
|
14
|
+
from .provider import ManifestProvider
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CAPABILITIES",
|
|
18
|
+
"Backend",
|
|
19
|
+
"CapabilityError",
|
|
20
|
+
"Executable",
|
|
21
|
+
"KernelLauncher",
|
|
22
|
+
"ManifestProvider",
|
|
23
|
+
"Marshal",
|
|
24
|
+
]
|