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
85 changes: 10 additions & 75 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,17 @@ jobs:
# JOB ORDERING RATIONALE (Fail-Fast Optimization)
# =============================================================================
#
# Phase 1 - Quick Validation (parallel, ~5-10 min):
# - smoke-test: Basic import/syntax checks
# Phase 1 - Quick Validation (parallel, ~2-3 min):
# - lint: Code quality (ruff format, ruff check, mypy strict)
# - pyright: Type checking (complementary to mypy)
# - exports-validation: __all__ exports validation
# - docs-validation: Documentation link validation
# - node-purity-check: Declarative node purity guarantees
# - mypy-validation-scripts: Type check validation scripts
#
# Phase 2 - Expensive Tests (after Phase 1 completes):
# Phase 2 - Full Test Suite (after Phase 1 completes):
# - test-parallel: 20 splits of full test suite (~3 min each)
# Depends on: smoke-test, lint, pyright, exports-validation
# Depends on: lint, pyright, exports-validation
# This ensures type errors and lint failures are caught BEFORE
# spawning 20 parallel test runners (saves ~60 runner-minutes on failure)
#
Expand All @@ -47,65 +46,6 @@ jobs:
# 3. All Phase 1 jobs run in parallel for speed
# =============================================================================

# Quick smoke test - fails fast on basic issues (5-10s)
smoke-test:
name: Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true

# Note: omnibase_spi is now public - no auth required

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: >-
venv-${{ runner.os }}-${{ env.PYTHON_VERSION }}-
${{ hashFiles('**/poetry.lock') }}-
${{ env.CACHE_VERSION }}
restore-keys: |
venv-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/poetry.lock') }}-${{ env.CACHE_VERSION }}

- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction --no-root || {
echo "❌ Dependency install failed"
exit 1
}

- name: Install project
run: |
poetry install --no-interaction || {
echo "❌ Project install failed"
exit 1
}

- name: Run smoke tests
run: |
poetry run pytest tests/unit/enums tests/unit/errors \
--maxfail=5 \
-x \
--tb=short

# Parallel test execution - split into 20 groups for speed and memory management
# Split strategy rationale:
# - 12,198 total tests / 20 splits = ~610 tests/split
Expand All @@ -115,15 +55,14 @@ jobs:
# - Shorter per-split runtime reduces probability of external runner shutdown
#
# Dependency rationale (fail-fast optimization):
# - smoke-test: Catches basic import/syntax errors before expensive splits
# - lint: Catches formatting, linting, and mypy errors early
# - pyright: Catches type errors that mypy might miss
# - exports-validation: Ensures __all__ exports are valid before tests
# These 4 jobs run in parallel (~10 min total) and must all pass before
# These 3 jobs run in parallel (~3 min total) and must all pass before
# spawning 20 test runners. This saves ~60 runner-minutes on validation failures.
test-parallel:
name: Tests (Split ${{ matrix.split }}/20)
needs: [smoke-test, lint, pyright, exports-validation]
needs: [lint, pyright, exports-validation]
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
Expand Down Expand Up @@ -205,7 +144,7 @@ jobs:
path: junit-${{ matrix.split }}.xml
retention-days: 7

# Phase 1 validation job - runs in parallel with smoke-test, pyright, exports-validation
# Phase 1 validation job - runs in parallel with pyright, exports-validation
# Must complete before test-parallel starts (fail-fast optimization)
lint:
name: Code Quality
Expand Down Expand Up @@ -259,7 +198,7 @@ jobs:
- name: Run mypy type checking (strict)
run: poetry run mypy src/omnibase_core

# Phase 1 validation job - runs in parallel with smoke-test, lint, exports-validation
# Phase 1 validation job - runs in parallel with lint, exports-validation
# Must complete before test-parallel starts (fail-fast optimization)
# Pyright complements mypy (in lint job) - catches different type error categories
pyright:
Expand Down Expand Up @@ -306,7 +245,7 @@ jobs:
- name: Run pyright type checking
run: poetry run pyright src/omnibase_core

# Phase 1 validation job - runs in parallel with smoke-test, lint, pyright
# Phase 1 validation job - runs in parallel with lint, pyright
# Must complete before test-parallel starts (fail-fast optimization)
# Quick check (~5 min) ensures __all__ exports are valid before expensive test splits
exports-validation:
Expand Down Expand Up @@ -607,13 +546,12 @@ jobs:
test-summary:
name: Test Summary
runs-on: ubuntu-latest
needs: [smoke-test, test-parallel, lint, pyright, exports-validation, mypy-validation-scripts, docs-validation, node-purity-check]
needs: [test-parallel, lint, pyright, exports-validation, mypy-validation-scripts, docs-validation, node-purity-check]
if: always()

steps:
- name: Check test results
run: |
smoke="${{ needs.smoke-test.result }}"
parallel="${{ needs.test-parallel.result }}"
lint="${{ needs.lint.result }}"
pyright="${{ needs.pyright.result }}"
Expand All @@ -622,16 +560,14 @@ jobs:
docs="${{ needs.docs-validation.result }}"
purity="${{ needs.node-purity-check.result }}"

if [[ "$smoke" == "success" ]] && \
[[ "$parallel" == "success" ]] && \
if [[ "$parallel" == "success" ]] && \
[[ "$lint" == "success" ]] && \
[[ "$pyright" == "success" ]] && \
[[ "$exports" == "success" ]] && \
[[ "$mypy_scripts" == "success" ]] && \
[[ "$docs" == "success" ]] && \
[[ "$purity" == "success" ]]; then
echo "✅ All tests passed successfully"
echo "Smoke Tests: ✅ Passed"
echo "Parallel Tests (20 splits): ✅ Passed"
echo "Code Quality: ✅ Passed"
echo "Pyright Type Checking: ✅ Passed"
Expand All @@ -642,7 +578,6 @@ jobs:
exit 0
else
echo "❌ Tests failed"
echo "Smoke Tests: $smoke"
echo "Parallel Tests: $parallel"
echo "Lint: $lint"
echo "Pyright: $pyright"
Expand Down
4 changes: 1 addition & 3 deletions docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ The core pipeline executor and transformation functions are designed to be **pur
- Each `execute_compute_pipeline()` invocation operates on its own data without shared state
- Safe for concurrent use from multiple threads

**Known Limitation (v1.0)**: The validation step warning deduplication uses module-level state (`_validation_warning_emitted`) to prevent log spam. This is a minor exception to the pure function principle, affecting only warning output (not computation results). The pipeline execution itself remains deterministic and thread-safe.

**TODO(v1.1)**: Refactor warning deduplication to use a context-based approach (e.g., per-execution context) rather than global state. See `compute_executor.py` for details.
**Note (v1.0)**: Validation steps emit a UserWarning indicating pass-through behavior. Warning deduplication is handled by Python's built-in `warnings` module, maintaining the pure function principle throughout the module.

---

Expand Down
24 changes: 7 additions & 17 deletions src/omnibase_core/utils/compute_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@

logger = logging.getLogger(__name__)

# Track if validation warning has been emitted for session deduplication
# TODO(): Refactor to context-based warning tracking instead of global state.
# This global mutable state is a known exception to the "pure and stateless" principle.
# It affects only warning output (not computation results) and pipeline execution
# remains deterministic. See: docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md
_validation_warning_emitted = False

from omnibase_core.enums.enum_compute_step_type import EnumComputeStepType
from omnibase_core.enums.enum_core_error_code import EnumCoreErrorCode
from omnibase_core.models.compute.model_compute_execution_context import (
Expand Down Expand Up @@ -355,16 +348,13 @@ def execute_validation_step(
# - Add validation error messages with path information
# See: docs/architecture/NODECOMPUTE_VERSIONING_ROADMAP.md

# Emit UserWarning once per session for validation steps
global _validation_warning_emitted
if not _validation_warning_emitted:
warnings.warn(
"Validation steps are pass-through in v1.0. "
"Schema validation will be implemented in v1.1.",
UserWarning,
stacklevel=2,
)
_validation_warning_emitted = True
# Emit UserWarning for validation steps (Python warnings module deduplicates automatically)
warnings.warn(
"Validation steps are pass-through in v1.0. "
"Schema validation will be implemented in v1.1.",
UserWarning,
stacklevel=2,
)

# Log debug-level message for each step (for troubleshooting)
logger.debug(
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/utils/test_compute_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,33 @@ def test_logger_exception_includes_exception_type(self) -> None:
# The logger.exception format string includes type(e).__name__
log_args = call_args[0][1:]
assert "ZeroDivisionError" in log_args


@pytest.mark.unit
@pytest.mark.timeout(5)
class TestValidationStepWarning:
"""Tests for validation step warning behavior."""

def test_validation_step_emits_warning(self) -> None:
"""Test that validation steps emit a UserWarning about v1.0 pass-through.

Verifies that execute_validation_step emits a warning indicating
validation is pass-through in v1.0, informing users that schema
validation will be implemented in v1.1.
"""
from omnibase_core.models.transformations.model_validation_step_config import (
ModelValidationStepConfig,
)
from omnibase_core.utils.compute_executor import execute_validation_step

step = ModelComputePipelineStep(
step_name="test_validation",
step_type=EnumComputeStepType.VALIDATION,
validation_config=ModelValidationStepConfig(schema_ref="test_schema"),
)

with pytest.warns(UserWarning, match="pass-through.*v1.1|v1.1.*pass-through"):
result = execute_validation_step(step, {"test": "data"})

# Verify pass-through behavior returns input unchanged
assert result == {"test": "data"}