diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fdc022f588..69ab7a931d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,8 +23,7 @@ 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 @@ -32,9 +31,9 @@ jobs: # - 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) # @@ -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 @@ -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: @@ -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 @@ -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: @@ -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: @@ -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 }}" @@ -622,8 +560,7 @@ 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" ]] && \ @@ -631,7 +568,6 @@ jobs: [[ "$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" @@ -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" diff --git a/docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md b/docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md index 35fb25e74b..a2961563a5 100644 --- a/docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md +++ b/docs/architecture/CONTRACT_DRIVEN_NODECOMPUTE_V1_0.md @@ -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. --- diff --git a/src/omnibase_core/utils/compute_executor.py b/src/omnibase_core/utils/compute_executor.py index 7147efeb7e..a71ae06dc0 100644 --- a/src/omnibase_core/utils/compute_executor.py +++ b/src/omnibase_core/utils/compute_executor.py @@ -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 ( @@ -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( diff --git a/tests/unit/utils/test_compute_executor.py b/tests/unit/utils/test_compute_executor.py index 9cc45f9061..fd1a25b1ed 100644 --- a/tests/unit/utils/test_compute_executor.py +++ b/tests/unit/utils/test_compute_executor.py @@ -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"}