Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1301,10 +1301,22 @@ jobs:
# OMN-15233: see the smart-selection step above — the live `uses:` pin
# resolution test needs an authenticated token or it fails closed on
# HTTP 403 rate limiting. This step is the one that surfaced it.
#
# OMN-15410: NO positional path is passed to pytest here, deliberately.
# This step used to say `uv run pytest tests/`, which silently made
# this workflow — not pyproject.toml — the real definition of "the full
# suite": four collocated test roots (scripts/ci/tests, scripts/tests,
# scripts/runtime_build/tests, and the agent_actions root) existed on
# disk and were collectable, but no CI job ever named them, so 366
# tests never ran and 4 of them had been RED since 2026-04-02
# undetected. With no positional argument pytest inherits
# `testpaths` from pyproject.toml verbatim, so there is exactly one
# place to add a root. scripts/validation/validate_test_root_collection.py
# fails closed if a positional path is re-introduced here.
env:
GH_TOKEN: ${{ github.token }}
run: |
uv run pytest tests/ \
uv run pytest \
--ignore=tests/integration/docker \
-m "not slow and not chaos and not kafka and not performance" \
--splits ${{ needs.detect-changes.outputs.split_count }} \
Expand Down
4 changes: 2 additions & 2 deletions docker/runners/runner-image.lock.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"base_image_digest": "sha256:3ba65aa20f86a0fad9df2b2c259c613df006b2e6d0bfcc8a146afb8c525a9751",
"gh_version": "2.67.0",
"identity_digest": "cbf6b6a9f26f447f3b6a30fbce4bcdb2",
"identity_digest": "a06e80a006bbeb1d7d6b0a01b88c2534",
"image_version": 6,
"kubectl_version": "1.32.1",
"python_version": "3.12",
"runner_version": "2.334.0",
"shared_env_digest": "46df2ce98c6e009e9eef2cd8",
"shared_env_digest": "1d384cdf9c6e10fcf7d1475f",
"shared_env_install_args": "--frozen --all-extras --all-groups --no-install-project",
"uv_version": "0.6.14"
}
21 changes: 20 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,26 @@ split-on-trailing-comma = true

[tool.pytest.ini_options]
pythonpath = ["src", "."]
testpaths = ["tests"]
# OMN-15410: `testpaths` is the SINGLE SOURCE OF TRUTH for what CI collects.
# The full-suite job in .github/workflows/ci.yml deliberately passes NO
# positional path to pytest so it inherits this list verbatim; previously it
# hardcoded `pytest tests/`, which meant a root added here would still never
# run. scripts/validation/validate_test_root_collection.py fails closed on
# both halves of that seam (a tests/ dir absent from this list, and a ci.yml
# full-suite step that re-introduces a positional path).
#
# The four non-`tests/` entries are collocated roots that were uncollected by
# every CI job until OMN-15410 (the OMN-15378 class). They cannot be folded
# into tests/ as-is: scripts/tests/ and the agent_actions root declare their
# own packages, and moving them would break their relative imports for no
# collection benefit now that this list governs.
testpaths = [
"tests",
"scripts/ci/tests",
"scripts/tests",
"scripts/runtime_build/tests",
"src/omnibase_infra/services/observability/agent_actions/tests",
]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
Expand Down
32 changes: 32 additions & 0 deletions scripts/ci/detect_test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@
"config/runner_routing_policy.yaml",
)

# OMN-15410: pytest roots that live NEXT TO the code they cover instead of
# under tests/. They are collected by the full suite (pyproject.toml
# `testpaths`), but the full suite is only one of two pytest steps — a
# NARROWED smart-selection run reaches nothing it is not explicitly told to
# reach. Without these mappings the four roots would be "collected" in the
# weakest possible sense: exercised only when something else escalated the
# job to full suite. Keys are source prefixes, values are the test roots a
# change under that prefix must run. Over-selection here is safe (extra tests
# run); under-selection is the OMN-15378 false-green class.
#
# Every value MUST also appear in pyproject.toml `testpaths`, and every
# non-`tests` testpaths entry MUST appear as a value here — both directions
# are asserted by scripts/validation/validate_test_root_collection.py.
COLLOCATED_TEST_ROOTS: dict[str, str] = {
# Broadest first is irrelevant (all matches apply), but note scripts/tests/
# covers the seed/keycloak scripts that live directly under scripts/, so it
# is mapped from the whole scripts/ tree, matching SCRIPTS_TEST_PREFIXES.
"scripts/": "scripts/tests/",
"scripts/ci/": "scripts/ci/tests/",
"scripts/runtime_build/": "scripts/runtime_build/tests/",
"src/omnibase_infra/services/observability/agent_actions/": (
"src/omnibase_infra/services/observability/agent_actions/tests/"
),
}

# Test families the change-aware pytest job structurally cannot run, so
# selecting one can never make it execute -- it would only make pytest exit 5
# ("no tests ran") when it is the sole selected path, reddening the gate without
Expand Down Expand Up @@ -186,6 +211,13 @@ def _resolve(
# scripts/ci/ keeps its tests/ci/ CI-process mapping AND gains these.
selected.update(SCRIPTS_TEST_PREFIXES)

# OMN-15410: collocated roots (tests living beside their code rather
# than under tests/). Independent of every branch above — a path can
# legitimately map to a tests/ directory AND to its collocated root.
for source_prefix, collocated_root in COLLOCATED_TEST_ROOTS.items():
if path.startswith(source_prefix):
selected.add(collocated_root)

expanded: set[str] = set(direct_modules)
for module in direct_modules:
expanded.update(config.adjacency[module].reverse_deps)
Expand Down
15 changes: 14 additions & 1 deletion scripts/ci/test_selection_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,22 @@ class EnumFullSuiteReason(StrEnum):
CHANGED_TEST_UNNARROWABLE = "changed_test_unnarrowable"


# A selectable pytest target: a directory under the root-collected `tests/`
# tree, OR a collocated `tests/` directory anywhere in the repo.
#
# OMN-15410 added the second alternative. The original `tests/`-only pattern
# encoded an assumption that stopped being true when pyproject `testpaths`
# grew to include four collocated roots (scripts/ci/tests/, scripts/tests/,
# scripts/runtime_build/tests/, and the agent_actions root): the selector could
# not emit them, so a narrowed run could never reach them and constructing the
# selection raised a pattern_mismatch ValidationError. The constraint stays
# tight — the final path component must still be `tests`, so the selector can
# never emit an arbitrary source directory to pytest.
TestPath = Annotated[
str,
StringConstraints(pattern=r"^tests(/[A-Za-z0-9_./-]+)?/$|^tests/$"),
StringConstraints(
pattern=r"^tests(/[A-Za-z0-9_./-]+)?/$|^[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)*/tests/$"
),
]
ModuleName = Annotated[
str,
Expand Down
Loading
Loading