diff --git a/.github/workflows/lifecycle.yml b/.github/workflows/lifecycle.yml index 6b92bea0..8fcafd50 100644 --- a/.github/workflows/lifecycle.yml +++ b/.github/workflows/lifecycle.yml @@ -30,6 +30,7 @@ jobs: lifecycle: name: Lifecycle (${{ inputs.mode || 'cli' }} / ${{ inputs.model || 'haiku' }}) runs-on: ubuntu-latest + environment: staging timeout-minutes: 60 steps: @@ -73,13 +74,17 @@ jobs: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} CODEFRAME_LIFECYCLE_MODEL: ${{ inputs.model || 'haiku' }} run: | + mkdir -p artifacts uv run pytest ${{ steps.tests.outputs.path }} \ -m lifecycle \ -v \ --tb=long \ --no-header \ -p no:warnings \ - --timeout=1800 + --timeout=1800 \ + --basetemp=artifacts/pytest \ + 2>&1 | tee artifacts/pytest.log + exit "${PIPESTATUS[0]}" - name: Upload test artifacts on failure if: failure() @@ -87,5 +92,6 @@ jobs: with: name: lifecycle-failure-${{ github.run_id }} path: | - /tmp/pytest-*/ + artifacts/ retention-days: 7 + if-no-files-found: warn diff --git a/pyproject.toml b/pyproject.toml index f2788bfe..968e1937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ dev = [ "pytest-asyncio>=0.23.0", "pytest-cov>=4.1.0", "pytest-json-report>=1.5.0", + "pytest-timeout>=2.3.0", "black>=24.1.0", "ruff>=0.2.0", "mypy>=1.8.0", diff --git a/tests/lifecycle/conftest.py b/tests/lifecycle/conftest.py index 8aadaed3..f5a361df 100644 --- a/tests/lifecycle/conftest.py +++ b/tests/lifecycle/conftest.py @@ -94,8 +94,8 @@ def initialized_workspace(target_project_dir, cf, sample_prd_path): A workspace with: - cf init complete - PRD added - - Tasks generated - Ready for `cf work batch run --all-ready --execute`. + - Tasks generated and promoted from BACKLOG → READY + Ready for `cf work batch run --all-ready`. """ result = cf("init", str(target_project_dir), "--tech-stack", "Python with uv") assert result.returncode == 0, f"cf init failed:\n{result.stderr}" @@ -110,4 +110,9 @@ def initialized_workspace(target_project_dir, cf, sample_prd_path): result = cf("tasks", "generate") assert result.returncode == 0, f"cf tasks generate failed:\n{result.stderr}" + # `cf tasks generate` creates BACKLOG tasks; promote to READY so the + # batch runner picks them up via --all-ready. + result = cf("tasks", "set", "status", "READY", "--all", "--from", "BACKLOG") + assert result.returncode == 0, f"cf tasks set status READY failed:\n{result.stderr}" + return target_project_dir diff --git a/tests/lifecycle/test_cli_lifecycle.py b/tests/lifecycle/test_cli_lifecycle.py index 0e3047ec..55820ea5 100644 --- a/tests/lifecycle/test_cli_lifecycle.py +++ b/tests/lifecycle/test_cli_lifecycle.py @@ -39,7 +39,6 @@ def test_agent_builds_csv_stats_project(self, initialized_workspace, cf): result = cf( "work", "batch", "run", "--all-ready", - "--execute", "--engine", "react", "--retry", "1", timeout=1800, # 30 min ceiling @@ -68,7 +67,7 @@ def test_agent_task_status_after_execution(self, initialized_workspace, cf): """All tasks reach DONE or BLOCKED status — none stuck in IN_PROGRESS.""" cf( "work", "batch", "run", - "--all-ready", "--execute", "--engine", "react", + "--all-ready", "--engine", "react", timeout=1800, ) diff --git a/uv.lock b/uv.lock index b90fbbb3..ee7e0859 100644 --- a/uv.lock +++ b/uv.lock @@ -614,6 +614,7 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-json-report" }, + { name = "pytest-timeout" }, { name = "ruff" }, ] @@ -652,6 +653,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, { name = "pytest-json-report", specifier = ">=1.5.0" }, { name = "pytest-json-report", marker = "extra == 'dev'", specifier = ">=1.5.0" }, + { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.3.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "python-jose", extras = ["cryptography"], specifier = ">=3.4.0" }, { name = "pyyaml", specifier = ">=6.0.0" }, @@ -2294,6 +2296,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" }, ] +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"