solokit 0.1.1__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.
- solokit/__init__.py +10 -0
- solokit/__version__.py +3 -0
- solokit/cli.py +374 -0
- solokit/core/__init__.py +1 -0
- solokit/core/cache.py +102 -0
- solokit/core/command_runner.py +278 -0
- solokit/core/config.py +453 -0
- solokit/core/config_validator.py +204 -0
- solokit/core/constants.py +291 -0
- solokit/core/error_formatter.py +279 -0
- solokit/core/error_handlers.py +346 -0
- solokit/core/exceptions.py +1567 -0
- solokit/core/file_ops.py +309 -0
- solokit/core/logging_config.py +166 -0
- solokit/core/output.py +99 -0
- solokit/core/performance.py +57 -0
- solokit/core/protocols.py +141 -0
- solokit/core/types.py +312 -0
- solokit/deployment/__init__.py +1 -0
- solokit/deployment/executor.py +411 -0
- solokit/git/__init__.py +1 -0
- solokit/git/integration.py +619 -0
- solokit/init/__init__.py +41 -0
- solokit/init/claude_commands_installer.py +87 -0
- solokit/init/dependency_installer.py +313 -0
- solokit/init/docs_structure.py +90 -0
- solokit/init/env_generator.py +160 -0
- solokit/init/environment_validator.py +334 -0
- solokit/init/git_hooks_installer.py +71 -0
- solokit/init/git_setup.py +188 -0
- solokit/init/gitignore_updater.py +195 -0
- solokit/init/initial_commit.py +145 -0
- solokit/init/initial_scans.py +109 -0
- solokit/init/orchestrator.py +246 -0
- solokit/init/readme_generator.py +207 -0
- solokit/init/session_structure.py +239 -0
- solokit/init/template_installer.py +424 -0
- solokit/learning/__init__.py +1 -0
- solokit/learning/archiver.py +115 -0
- solokit/learning/categorizer.py +126 -0
- solokit/learning/curator.py +428 -0
- solokit/learning/extractor.py +352 -0
- solokit/learning/reporter.py +351 -0
- solokit/learning/repository.py +254 -0
- solokit/learning/similarity.py +342 -0
- solokit/learning/validator.py +144 -0
- solokit/project/__init__.py +1 -0
- solokit/project/init.py +1162 -0
- solokit/project/stack.py +436 -0
- solokit/project/sync_plugin.py +438 -0
- solokit/project/tree.py +375 -0
- solokit/quality/__init__.py +1 -0
- solokit/quality/api_validator.py +424 -0
- solokit/quality/checkers/__init__.py +25 -0
- solokit/quality/checkers/base.py +114 -0
- solokit/quality/checkers/context7.py +221 -0
- solokit/quality/checkers/custom.py +162 -0
- solokit/quality/checkers/deployment.py +323 -0
- solokit/quality/checkers/documentation.py +179 -0
- solokit/quality/checkers/formatting.py +161 -0
- solokit/quality/checkers/integration.py +394 -0
- solokit/quality/checkers/linting.py +159 -0
- solokit/quality/checkers/security.py +261 -0
- solokit/quality/checkers/spec_completeness.py +127 -0
- solokit/quality/checkers/tests.py +184 -0
- solokit/quality/env_validator.py +306 -0
- solokit/quality/gates.py +655 -0
- solokit/quality/reporters/__init__.py +10 -0
- solokit/quality/reporters/base.py +25 -0
- solokit/quality/reporters/console.py +98 -0
- solokit/quality/reporters/json_reporter.py +34 -0
- solokit/quality/results.py +98 -0
- solokit/session/__init__.py +1 -0
- solokit/session/briefing/__init__.py +245 -0
- solokit/session/briefing/documentation_loader.py +53 -0
- solokit/session/briefing/formatter.py +476 -0
- solokit/session/briefing/git_context.py +282 -0
- solokit/session/briefing/learning_loader.py +212 -0
- solokit/session/briefing/milestone_builder.py +78 -0
- solokit/session/briefing/orchestrator.py +137 -0
- solokit/session/briefing/stack_detector.py +51 -0
- solokit/session/briefing/tree_generator.py +52 -0
- solokit/session/briefing/work_item_loader.py +209 -0
- solokit/session/briefing.py +353 -0
- solokit/session/complete.py +1188 -0
- solokit/session/status.py +246 -0
- solokit/session/validate.py +452 -0
- solokit/templates/.claude/commands/end.md +109 -0
- solokit/templates/.claude/commands/init.md +159 -0
- solokit/templates/.claude/commands/learn-curate.md +88 -0
- solokit/templates/.claude/commands/learn-search.md +62 -0
- solokit/templates/.claude/commands/learn-show.md +69 -0
- solokit/templates/.claude/commands/learn.md +136 -0
- solokit/templates/.claude/commands/start.md +114 -0
- solokit/templates/.claude/commands/status.md +22 -0
- solokit/templates/.claude/commands/validate.md +27 -0
- solokit/templates/.claude/commands/work-delete.md +119 -0
- solokit/templates/.claude/commands/work-graph.md +139 -0
- solokit/templates/.claude/commands/work-list.md +26 -0
- solokit/templates/.claude/commands/work-new.md +114 -0
- solokit/templates/.claude/commands/work-next.md +25 -0
- solokit/templates/.claude/commands/work-show.md +24 -0
- solokit/templates/.claude/commands/work-update.md +141 -0
- solokit/templates/CHANGELOG.md +17 -0
- solokit/templates/WORK_ITEM_TYPES.md +141 -0
- solokit/templates/__init__.py +1 -0
- solokit/templates/bug_spec.md +217 -0
- solokit/templates/config.schema.json +150 -0
- solokit/templates/dashboard_refine/base/.gitignore +36 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/layout.tsx +22 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/page.tsx +68 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/users/page.tsx +77 -0
- solokit/templates/dashboard_refine/base/app/globals.css +60 -0
- solokit/templates/dashboard_refine/base/app/layout.tsx +23 -0
- solokit/templates/dashboard_refine/base/app/page.tsx +9 -0
- solokit/templates/dashboard_refine/base/components/client-refine-wrapper.tsx +21 -0
- solokit/templates/dashboard_refine/base/components/layout/header.tsx +44 -0
- solokit/templates/dashboard_refine/base/components/layout/sidebar.tsx +82 -0
- solokit/templates/dashboard_refine/base/components/ui/button.tsx +53 -0
- solokit/templates/dashboard_refine/base/components/ui/card.tsx +78 -0
- solokit/templates/dashboard_refine/base/components/ui/table.tsx +116 -0
- solokit/templates/dashboard_refine/base/components.json +16 -0
- solokit/templates/dashboard_refine/base/lib/refine.tsx +65 -0
- solokit/templates/dashboard_refine/base/lib/utils.ts +13 -0
- solokit/templates/dashboard_refine/base/next.config.ts +10 -0
- solokit/templates/dashboard_refine/base/package.json.template +40 -0
- solokit/templates/dashboard_refine/base/postcss.config.mjs +8 -0
- solokit/templates/dashboard_refine/base/providers/refine-provider.tsx +26 -0
- solokit/templates/dashboard_refine/base/tailwind.config.ts +57 -0
- solokit/templates/dashboard_refine/base/tsconfig.json +27 -0
- solokit/templates/dashboard_refine/docker/Dockerfile +57 -0
- solokit/templates/dashboard_refine/docker/docker-compose.prod.yml +31 -0
- solokit/templates/dashboard_refine/docker/docker-compose.yml +21 -0
- solokit/templates/dashboard_refine/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/dashboard_refine/tier-1-essential/package.json.tier1.template +57 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/setup.ts +26 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/unit/example.test.tsx +73 -0
- solokit/templates/dashboard_refine/tier-2-standard/package.json.tier2.template +62 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/eslint.config.mjs +22 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/package.json.tier3.template +79 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/stryker.conf.json +38 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/dashboard.spec.ts +88 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/user-management.spec.ts +102 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/integration/dashboard.test.tsx +90 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/type-coverage.json +16 -0
- solokit/templates/dashboard_refine/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/dashboard_refine/tier-4-production/k6/dashboard-load-test.js +70 -0
- solokit/templates/dashboard_refine/tier-4-production/next.config.ts +46 -0
- solokit/templates/dashboard_refine/tier-4-production/package.json.tier4.template +89 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/deployment_spec.md +500 -0
- solokit/templates/feature_spec.md +248 -0
- solokit/templates/fullstack_nextjs/base/.gitignore +36 -0
- solokit/templates/fullstack_nextjs/base/app/api/example/route.ts +65 -0
- solokit/templates/fullstack_nextjs/base/app/globals.css +27 -0
- solokit/templates/fullstack_nextjs/base/app/layout.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/app/page.tsx +32 -0
- solokit/templates/fullstack_nextjs/base/components/example-component.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/lib/prisma.ts +17 -0
- solokit/templates/fullstack_nextjs/base/lib/utils.ts +13 -0
- solokit/templates/fullstack_nextjs/base/lib/validations.ts +20 -0
- solokit/templates/fullstack_nextjs/base/next.config.ts +7 -0
- solokit/templates/fullstack_nextjs/base/package.json.template +32 -0
- solokit/templates/fullstack_nextjs/base/postcss.config.mjs +8 -0
- solokit/templates/fullstack_nextjs/base/prisma/schema.prisma +21 -0
- solokit/templates/fullstack_nextjs/base/tailwind.config.ts +19 -0
- solokit/templates/fullstack_nextjs/base/tsconfig.json +27 -0
- solokit/templates/fullstack_nextjs/docker/Dockerfile +60 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.prod.yml +57 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.yml +47 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/package.json.tier1.template +48 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/api/example.test.ts +88 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/unit/example.test.tsx +22 -0
- solokit/templates/fullstack_nextjs/tier-2-standard/package.json.tier2.template +52 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/package.json.tier3.template +68 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/stryker.conf.json +33 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/e2e/flow.spec.ts +59 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/integration/api.test.ts +165 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/fullstack_nextjs/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/fullstack_nextjs/tier-4-production/k6/load-test.js +45 -0
- solokit/templates/fullstack_nextjs/tier-4-production/next.config.ts +46 -0
- solokit/templates/fullstack_nextjs/tier-4-production/package.json.tier4.template +77 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/git-hooks/prepare-commit-msg +24 -0
- solokit/templates/integration_test_spec.md +363 -0
- solokit/templates/learnings.json +15 -0
- solokit/templates/ml_ai_fastapi/base/.gitignore +104 -0
- solokit/templates/ml_ai_fastapi/base/alembic/env.py +96 -0
- solokit/templates/ml_ai_fastapi/base/alembic.ini +114 -0
- solokit/templates/ml_ai_fastapi/base/pyproject.toml.template +91 -0
- solokit/templates/ml_ai_fastapi/base/requirements.txt.template +28 -0
- solokit/templates/ml_ai_fastapi/base/src/__init__.py +5 -0
- solokit/templates/ml_ai_fastapi/base/src/api/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/dependencies.py +20 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/example.py +134 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/health.py +66 -0
- solokit/templates/ml_ai_fastapi/base/src/core/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/core/config.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/core/database.py +50 -0
- solokit/templates/ml_ai_fastapi/base/src/main.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/models/__init__.py +7 -0
- solokit/templates/ml_ai_fastapi/base/src/models/example.py +61 -0
- solokit/templates/ml_ai_fastapi/base/src/services/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/services/example.py +115 -0
- solokit/templates/ml_ai_fastapi/docker/Dockerfile +59 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.prod.yml +112 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.yml +77 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyproject.toml.tier1.template +112 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyrightconfig.json +41 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pytest.ini +69 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/requirements-dev.txt +17 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/ruff.toml +81 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/conftest.py +72 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/test_main.py +49 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/test_example.py +113 -0
- solokit/templates/ml_ai_fastapi/tier-2-standard/pyproject.toml.tier2.template +130 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/locustfile.py +99 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/mutmut_config.py +53 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/pyproject.toml.tier3.template +150 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/conftest.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/test_api.py +131 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/pyproject.toml.tier4.template +162 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/requirements-prod.txt +25 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/api/routes/metrics.py +19 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/logging.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/monitoring.py +68 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/sentry.py +66 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/logging.py +79 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/tracing.py +60 -0
- solokit/templates/refactor_spec.md +287 -0
- solokit/templates/saas_t3/base/.gitignore +36 -0
- solokit/templates/saas_t3/base/app/api/trpc/[trpc]/route.ts +33 -0
- solokit/templates/saas_t3/base/app/globals.css +27 -0
- solokit/templates/saas_t3/base/app/layout.tsx +23 -0
- solokit/templates/saas_t3/base/app/page.tsx +31 -0
- solokit/templates/saas_t3/base/lib/api.tsx +77 -0
- solokit/templates/saas_t3/base/lib/utils.ts +13 -0
- solokit/templates/saas_t3/base/next.config.ts +7 -0
- solokit/templates/saas_t3/base/package.json.template +38 -0
- solokit/templates/saas_t3/base/postcss.config.mjs +8 -0
- solokit/templates/saas_t3/base/prisma/schema.prisma +20 -0
- solokit/templates/saas_t3/base/server/api/root.ts +19 -0
- solokit/templates/saas_t3/base/server/api/routers/example.ts +28 -0
- solokit/templates/saas_t3/base/server/api/trpc.ts +52 -0
- solokit/templates/saas_t3/base/server/db.ts +17 -0
- solokit/templates/saas_t3/base/tailwind.config.ts +19 -0
- solokit/templates/saas_t3/base/tsconfig.json +27 -0
- solokit/templates/saas_t3/docker/Dockerfile +60 -0
- solokit/templates/saas_t3/docker/docker-compose.prod.yml +59 -0
- solokit/templates/saas_t3/docker/docker-compose.yml +49 -0
- solokit/templates/saas_t3/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/saas_t3/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/saas_t3/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/saas_t3/tier-1-essential/package.json.tier1.template +54 -0
- solokit/templates/saas_t3/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/saas_t3/tier-1-essential/tests/unit/example.test.tsx +24 -0
- solokit/templates/saas_t3/tier-2-standard/package.json.tier2.template +58 -0
- solokit/templates/saas_t3/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/saas_t3/tier-3-comprehensive/package.json.tier3.template +74 -0
- solokit/templates/saas_t3/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/saas_t3/tier-3-comprehensive/stryker.conf.json +34 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/e2e/home.spec.ts +41 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/integration/api.test.ts +44 -0
- solokit/templates/saas_t3/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/saas_t3/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/saas_t3/tier-4-production/k6/load-test.js +51 -0
- solokit/templates/saas_t3/tier-4-production/next.config.ts +46 -0
- solokit/templates/saas_t3/tier-4-production/package.json.tier4.template +83 -0
- solokit/templates/saas_t3/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/saas_t3/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/vercel.json +37 -0
- solokit/templates/security_spec.md +287 -0
- solokit/templates/stack-versions.yaml +617 -0
- solokit/templates/status_update.json +6 -0
- solokit/templates/template-registry.json +257 -0
- solokit/templates/work_items.json +11 -0
- solokit/testing/__init__.py +1 -0
- solokit/testing/integration_runner.py +550 -0
- solokit/testing/performance.py +637 -0
- solokit/visualization/__init__.py +1 -0
- solokit/visualization/dependency_graph.py +788 -0
- solokit/work_items/__init__.py +1 -0
- solokit/work_items/creator.py +217 -0
- solokit/work_items/delete.py +264 -0
- solokit/work_items/get_dependencies.py +185 -0
- solokit/work_items/get_dependents.py +113 -0
- solokit/work_items/get_metadata.py +121 -0
- solokit/work_items/get_next_recommendations.py +133 -0
- solokit/work_items/manager.py +235 -0
- solokit/work_items/milestones.py +137 -0
- solokit/work_items/query.py +376 -0
- solokit/work_items/repository.py +267 -0
- solokit/work_items/scheduler.py +184 -0
- solokit/work_items/spec_parser.py +838 -0
- solokit/work_items/spec_validator.py +493 -0
- solokit/work_items/updater.py +157 -0
- solokit/work_items/validator.py +205 -0
- solokit-0.1.1.dist-info/METADATA +640 -0
- solokit-0.1.1.dist-info/RECORD +323 -0
- solokit-0.1.1.dist-info/WHEEL +5 -0
- solokit-0.1.1.dist-info/entry_points.txt +2 -0
- solokit-0.1.1.dist-info/licenses/LICENSE +21 -0
- solokit-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Integration test execution framework.
|
|
4
|
+
|
|
5
|
+
Supports:
|
|
6
|
+
- Multi-service orchestration
|
|
7
|
+
- Test environment setup/teardown
|
|
8
|
+
- Test data management
|
|
9
|
+
- Parallel test execution
|
|
10
|
+
- Result aggregation
|
|
11
|
+
|
|
12
|
+
Updated in Phase 5.7.3 to use spec_parser for reading test specifications.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from solokit.core.command_runner import CommandRunner
|
|
26
|
+
from solokit.core.constants import (
|
|
27
|
+
CLEANUP_TIMEOUT,
|
|
28
|
+
DOCKER_COMMAND_TIMEOUT,
|
|
29
|
+
DOCKER_COMPOSE_TIMEOUT,
|
|
30
|
+
FIXTURE_SETUP_TIMEOUT,
|
|
31
|
+
INTEGRATION_TEST_TIMEOUT,
|
|
32
|
+
)
|
|
33
|
+
from solokit.core.exceptions import (
|
|
34
|
+
EnvironmentSetupError,
|
|
35
|
+
FileNotFoundError,
|
|
36
|
+
IntegrationExecutionError,
|
|
37
|
+
TimeoutError,
|
|
38
|
+
ValidationError,
|
|
39
|
+
)
|
|
40
|
+
from solokit.core.output import get_output
|
|
41
|
+
from solokit.work_items import spec_parser
|
|
42
|
+
|
|
43
|
+
output = get_output()
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IntegrationTestRunner:
|
|
48
|
+
"""Execute integration tests with multi-service orchestration."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, work_item: dict):
|
|
51
|
+
"""
|
|
52
|
+
Initialize integration test runner.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
work_item: Integration test work item (must have 'id' field)
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValidationError: If work item missing 'id' field or spec parsing fails
|
|
59
|
+
FileNotFoundError: If spec file not found
|
|
60
|
+
"""
|
|
61
|
+
self.work_item = work_item
|
|
62
|
+
work_id = work_item.get("id")
|
|
63
|
+
|
|
64
|
+
if not work_id:
|
|
65
|
+
raise ValidationError(
|
|
66
|
+
message="Work item must have 'id' field",
|
|
67
|
+
context={"work_item": work_item},
|
|
68
|
+
remediation="Ensure work item dict contains 'id' key",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Parse spec file to get test scenarios and environment requirements
|
|
72
|
+
# Pass full work_item dict to support custom spec filenames
|
|
73
|
+
try:
|
|
74
|
+
parsed_spec = spec_parser.parse_spec_file(work_item)
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
# Re-raise as-is (already correct exception type)
|
|
77
|
+
raise
|
|
78
|
+
except Exception as e:
|
|
79
|
+
raise ValidationError(
|
|
80
|
+
message=f"Failed to parse spec file for {work_id}",
|
|
81
|
+
context={"work_item_id": work_id, "error": str(e)},
|
|
82
|
+
remediation=f"Check .session/specs/{work_id}.md for valid format",
|
|
83
|
+
cause=e,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Extract test scenarios from parsed spec
|
|
87
|
+
self.test_scenarios = parsed_spec.get("test_scenarios", [])
|
|
88
|
+
|
|
89
|
+
# Parse environment requirements from spec content
|
|
90
|
+
# The environment_requirements section contains service names and configuration
|
|
91
|
+
env_req_text = parsed_spec.get("environment_requirements", "")
|
|
92
|
+
self.env_requirements = self._parse_environment_requirements(env_req_text)
|
|
93
|
+
|
|
94
|
+
self.results: dict[str, Any] = {
|
|
95
|
+
"scenarios": [],
|
|
96
|
+
"start_time": None,
|
|
97
|
+
"end_time": None,
|
|
98
|
+
"total_duration": 0.0,
|
|
99
|
+
"passed": 0,
|
|
100
|
+
"failed": 0,
|
|
101
|
+
"skipped": 0,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Initialize CommandRunner
|
|
105
|
+
self.runner = CommandRunner(default_timeout=INTEGRATION_TEST_TIMEOUT)
|
|
106
|
+
|
|
107
|
+
def _parse_environment_requirements(self, env_text: str) -> dict:
|
|
108
|
+
"""
|
|
109
|
+
Parse environment requirements from spec text.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
env_text: Environment requirements section content
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dict with 'services_required' and 'compose_file' keys
|
|
116
|
+
"""
|
|
117
|
+
if not env_text:
|
|
118
|
+
return {
|
|
119
|
+
"services_required": [],
|
|
120
|
+
"compose_file": "docker-compose.integration.yml",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Extract service names (look for lines with service names)
|
|
124
|
+
services = []
|
|
125
|
+
compose_file = "docker-compose.integration.yml"
|
|
126
|
+
|
|
127
|
+
for line in env_text.split("\n"):
|
|
128
|
+
line = line.strip()
|
|
129
|
+
# Look for service names (simple heuristic: lines with common service names)
|
|
130
|
+
if any(
|
|
131
|
+
s in line.lower()
|
|
132
|
+
for s in [
|
|
133
|
+
"postgresql",
|
|
134
|
+
"postgres",
|
|
135
|
+
"redis",
|
|
136
|
+
"mongodb",
|
|
137
|
+
"mysql",
|
|
138
|
+
"nginx",
|
|
139
|
+
"kafka",
|
|
140
|
+
]
|
|
141
|
+
):
|
|
142
|
+
# Extract service name and version if present
|
|
143
|
+
parts = line.split()
|
|
144
|
+
if parts:
|
|
145
|
+
services.append(parts[0].strip("-*•"))
|
|
146
|
+
# Look for compose file reference
|
|
147
|
+
if "docker-compose" in line.lower() or "compose" in line.lower():
|
|
148
|
+
# Try to extract filename
|
|
149
|
+
words = line.split()
|
|
150
|
+
for word in words:
|
|
151
|
+
if "docker-compose" in word or word.endswith(".yml") or word.endswith(".yaml"):
|
|
152
|
+
compose_file = word.strip("`\"':")
|
|
153
|
+
|
|
154
|
+
return {"services_required": services, "compose_file": compose_file}
|
|
155
|
+
|
|
156
|
+
def setup_environment(self) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Set up integration test environment.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
FileNotFoundError: If Docker Compose file not found
|
|
162
|
+
EnvironmentSetupError: If service startup or health check fails
|
|
163
|
+
TimeoutError: If service startup times out
|
|
164
|
+
"""
|
|
165
|
+
logger.info("Setting up integration test environment...")
|
|
166
|
+
|
|
167
|
+
# Check if Docker Compose file exists
|
|
168
|
+
compose_file = self.env_requirements.get("compose_file", "docker-compose.integration.yml")
|
|
169
|
+
if not Path(compose_file).exists():
|
|
170
|
+
raise FileNotFoundError(file_path=compose_file, file_type="Docker Compose")
|
|
171
|
+
|
|
172
|
+
# Start services
|
|
173
|
+
result = self.runner.run(
|
|
174
|
+
["docker-compose", "-f", compose_file, "up", "-d"],
|
|
175
|
+
timeout=DOCKER_COMPOSE_TIMEOUT,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if not result.success:
|
|
179
|
+
if result.timed_out:
|
|
180
|
+
raise TimeoutError(
|
|
181
|
+
operation="docker-compose startup",
|
|
182
|
+
timeout_seconds=180,
|
|
183
|
+
context={"compose_file": compose_file},
|
|
184
|
+
)
|
|
185
|
+
raise EnvironmentSetupError(
|
|
186
|
+
component="docker-compose",
|
|
187
|
+
details=result.stderr or "Failed to start services",
|
|
188
|
+
context={"compose_file": compose_file, "stderr": result.stderr},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
logger.info(f"✓ Services started from {compose_file}")
|
|
192
|
+
|
|
193
|
+
# Wait for services to be healthy
|
|
194
|
+
services = self.env_requirements.get("services_required", [])
|
|
195
|
+
for service in services:
|
|
196
|
+
self._wait_for_service(service)
|
|
197
|
+
|
|
198
|
+
logger.info(f"✓ All {len(services)} services are healthy")
|
|
199
|
+
|
|
200
|
+
# Load test data
|
|
201
|
+
self._load_test_data()
|
|
202
|
+
|
|
203
|
+
logger.info("✓ Integration test environment ready")
|
|
204
|
+
|
|
205
|
+
def _wait_for_service(self, service: str, timeout: int = 60) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Wait for service to be healthy.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
service: Service name
|
|
211
|
+
timeout: Maximum wait time in seconds
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
TimeoutError: If service doesn't become healthy within timeout
|
|
215
|
+
EnvironmentSetupError: If service health check fails
|
|
216
|
+
"""
|
|
217
|
+
start_time = time.time()
|
|
218
|
+
|
|
219
|
+
while time.time() - start_time < timeout:
|
|
220
|
+
result = self.runner.run(
|
|
221
|
+
["docker-compose", "ps", "-q", service], timeout=DOCKER_COMMAND_TIMEOUT
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if result.success and result.stdout.strip():
|
|
225
|
+
# Check health status
|
|
226
|
+
health_result = self.runner.run(
|
|
227
|
+
[
|
|
228
|
+
"docker",
|
|
229
|
+
"inspect",
|
|
230
|
+
"--format='{{.State.Health.Status}}'",
|
|
231
|
+
result.stdout.strip(),
|
|
232
|
+
],
|
|
233
|
+
timeout=DOCKER_COMMAND_TIMEOUT,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if health_result.success and "healthy" in health_result.stdout:
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
time.sleep(2)
|
|
240
|
+
|
|
241
|
+
# Timeout - service didn't become healthy
|
|
242
|
+
raise TimeoutError(
|
|
243
|
+
operation=f"waiting for service '{service}' to become healthy",
|
|
244
|
+
timeout_seconds=timeout,
|
|
245
|
+
context={"service": service},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _load_test_data(self) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Load test data fixtures.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
FileNotFoundError: If fixture file not found
|
|
254
|
+
EnvironmentSetupError: If fixture loading fails
|
|
255
|
+
"""
|
|
256
|
+
fixtures = self.env_requirements.get("test_data_fixtures", [])
|
|
257
|
+
|
|
258
|
+
for fixture in fixtures:
|
|
259
|
+
fixture_path = Path(fixture)
|
|
260
|
+
if not fixture_path.exists():
|
|
261
|
+
logger.warning(f"Fixture not found: {fixture}")
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
# Execute fixture loading script
|
|
265
|
+
result = self.runner.run(
|
|
266
|
+
["python", str(fixture_path)], timeout=FIXTURE_SETUP_TIMEOUT, check=True
|
|
267
|
+
)
|
|
268
|
+
if result.success:
|
|
269
|
+
logger.info(f"✓ Loaded fixture: {fixture}")
|
|
270
|
+
else:
|
|
271
|
+
raise EnvironmentSetupError(
|
|
272
|
+
component="test data fixture",
|
|
273
|
+
details=f"Failed to load fixture: {fixture}",
|
|
274
|
+
context={"fixture": fixture, "stderr": result.stderr},
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def run_tests(self, language: str | None = None) -> dict[str, Any]:
|
|
278
|
+
"""
|
|
279
|
+
Execute all integration test scenarios.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
language: Project language (python, javascript, typescript)
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
dict: Test results with passed/failed counts and duration
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
ValidationError: If unsupported language
|
|
289
|
+
IntegrationExecutionError: If tests fail to execute
|
|
290
|
+
"""
|
|
291
|
+
self.results["start_time"] = datetime.now().isoformat()
|
|
292
|
+
|
|
293
|
+
logger.info(f"\nRunning {len(self.test_scenarios)} integration test scenarios...\n")
|
|
294
|
+
|
|
295
|
+
# Detect language if not provided
|
|
296
|
+
if language is None:
|
|
297
|
+
language = self._detect_language()
|
|
298
|
+
|
|
299
|
+
# Run scenarios based on language
|
|
300
|
+
if language == "python":
|
|
301
|
+
self._run_pytest()
|
|
302
|
+
elif language in ["javascript", "typescript"]:
|
|
303
|
+
self._run_jest()
|
|
304
|
+
else:
|
|
305
|
+
raise ValidationError(
|
|
306
|
+
message=f"Unsupported language: {language}",
|
|
307
|
+
context={"language": language},
|
|
308
|
+
remediation="Supported languages: python, javascript, typescript",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
self.results["end_time"] = datetime.now().isoformat()
|
|
312
|
+
|
|
313
|
+
# Calculate duration
|
|
314
|
+
start = datetime.fromisoformat(self.results["start_time"])
|
|
315
|
+
end = datetime.fromisoformat(self.results["end_time"])
|
|
316
|
+
self.results["total_duration"] = (end - start).total_seconds()
|
|
317
|
+
|
|
318
|
+
return self.results
|
|
319
|
+
|
|
320
|
+
def _run_pytest(self) -> None:
|
|
321
|
+
"""
|
|
322
|
+
Run integration tests using pytest.
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
TimeoutError: If tests timeout
|
|
326
|
+
IntegrationExecutionError: If tests fail
|
|
327
|
+
"""
|
|
328
|
+
test_dir = self.work_item.get("test_directory", "tests/integration")
|
|
329
|
+
|
|
330
|
+
result = self.runner.run(
|
|
331
|
+
[
|
|
332
|
+
"pytest",
|
|
333
|
+
test_dir,
|
|
334
|
+
"-v",
|
|
335
|
+
"--tb=short",
|
|
336
|
+
"--json-report",
|
|
337
|
+
"--json-report-file=integration-test-results.json",
|
|
338
|
+
],
|
|
339
|
+
timeout=INTEGRATION_TEST_TIMEOUT,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Parse results
|
|
343
|
+
results_file = Path("integration-test-results.json")
|
|
344
|
+
if results_file.exists():
|
|
345
|
+
with open(results_file) as f:
|
|
346
|
+
test_data = json.load(f)
|
|
347
|
+
|
|
348
|
+
self.results["passed"] = test_data.get("summary", {}).get("passed", 0)
|
|
349
|
+
self.results["failed"] = test_data.get("summary", {}).get("failed", 0)
|
|
350
|
+
self.results["skipped"] = test_data.get("summary", {}).get("skipped", 0)
|
|
351
|
+
self.results["tests"] = test_data.get("tests", [])
|
|
352
|
+
|
|
353
|
+
if result.timed_out:
|
|
354
|
+
raise TimeoutError(
|
|
355
|
+
operation="pytest execution",
|
|
356
|
+
timeout_seconds=600,
|
|
357
|
+
context={"test_directory": test_dir},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if not result.success:
|
|
361
|
+
raise IntegrationExecutionError(
|
|
362
|
+
test_framework="pytest",
|
|
363
|
+
details=f"{self.results.get('failed', 0)} tests failed",
|
|
364
|
+
context={
|
|
365
|
+
"test_directory": test_dir,
|
|
366
|
+
"passed": self.results.get("passed", 0),
|
|
367
|
+
"failed": self.results.get("failed", 0),
|
|
368
|
+
"skipped": self.results.get("skipped", 0),
|
|
369
|
+
"stderr": result.stderr,
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def _run_jest(self) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Run integration tests using Jest.
|
|
376
|
+
|
|
377
|
+
Raises:
|
|
378
|
+
TimeoutError: If tests timeout
|
|
379
|
+
IntegrationExecutionError: If tests fail
|
|
380
|
+
"""
|
|
381
|
+
result = self.runner.run(
|
|
382
|
+
[
|
|
383
|
+
"npm",
|
|
384
|
+
"test",
|
|
385
|
+
"--",
|
|
386
|
+
"--testPathPattern=integration",
|
|
387
|
+
"--json",
|
|
388
|
+
"--outputFile=integration-test-results.json",
|
|
389
|
+
],
|
|
390
|
+
timeout=INTEGRATION_TEST_TIMEOUT,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Parse results
|
|
394
|
+
results_file = Path("integration-test-results.json")
|
|
395
|
+
if results_file.exists():
|
|
396
|
+
with open(results_file) as f:
|
|
397
|
+
test_data = json.load(f)
|
|
398
|
+
|
|
399
|
+
self.results["passed"] = test_data.get("numPassedTests", 0)
|
|
400
|
+
self.results["failed"] = test_data.get("numFailedTests", 0)
|
|
401
|
+
self.results["skipped"] = test_data.get("numPendingTests", 0)
|
|
402
|
+
|
|
403
|
+
if result.timed_out:
|
|
404
|
+
raise TimeoutError(
|
|
405
|
+
operation="jest execution",
|
|
406
|
+
timeout_seconds=600,
|
|
407
|
+
context={"test_pattern": "integration"},
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if not result.success:
|
|
411
|
+
raise IntegrationExecutionError(
|
|
412
|
+
test_framework="jest",
|
|
413
|
+
details=f"{self.results.get('failed', 0)} tests failed",
|
|
414
|
+
context={
|
|
415
|
+
"passed": self.results.get("passed", 0),
|
|
416
|
+
"failed": self.results.get("failed", 0),
|
|
417
|
+
"skipped": self.results.get("skipped", 0),
|
|
418
|
+
"stderr": result.stderr,
|
|
419
|
+
},
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def _detect_language(self) -> str:
|
|
423
|
+
"""Detect project language."""
|
|
424
|
+
if Path("pyproject.toml").exists() or Path("setup.py").exists():
|
|
425
|
+
return "python"
|
|
426
|
+
elif Path("package.json").exists():
|
|
427
|
+
if Path("tsconfig.json").exists():
|
|
428
|
+
return "typescript"
|
|
429
|
+
return "javascript"
|
|
430
|
+
return "python"
|
|
431
|
+
|
|
432
|
+
def teardown_environment(self) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Tear down integration test environment.
|
|
435
|
+
|
|
436
|
+
Raises:
|
|
437
|
+
EnvironmentSetupError: If teardown fails
|
|
438
|
+
TimeoutError: If teardown times out
|
|
439
|
+
"""
|
|
440
|
+
logger.info("\nTearing down integration test environment...")
|
|
441
|
+
|
|
442
|
+
compose_file = self.env_requirements.get("compose_file", "docker-compose.integration.yml")
|
|
443
|
+
|
|
444
|
+
# Stop and remove services
|
|
445
|
+
result = self.runner.run(
|
|
446
|
+
["docker-compose", "-f", compose_file, "down", "-v"],
|
|
447
|
+
timeout=CLEANUP_TIMEOUT,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if not result.success:
|
|
451
|
+
if result.timed_out:
|
|
452
|
+
raise TimeoutError(
|
|
453
|
+
operation="docker-compose teardown",
|
|
454
|
+
timeout_seconds=60,
|
|
455
|
+
context={"compose_file": compose_file},
|
|
456
|
+
)
|
|
457
|
+
raise EnvironmentSetupError(
|
|
458
|
+
component="docker-compose teardown",
|
|
459
|
+
details=result.stderr or "Failed to tear down services",
|
|
460
|
+
context={"compose_file": compose_file, "stderr": result.stderr},
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
logger.info("✓ Services stopped and removed")
|
|
464
|
+
logger.info("✓ Volumes cleaned up")
|
|
465
|
+
|
|
466
|
+
def generate_report(self) -> str:
|
|
467
|
+
"""Generate integration test report."""
|
|
468
|
+
report = f"""
|
|
469
|
+
Integration Test Report
|
|
470
|
+
{"=" * 80}
|
|
471
|
+
|
|
472
|
+
Work Item: {self.work_item.get("id", "N/A")}
|
|
473
|
+
Test Name: {self.work_item.get("title", "N/A")}
|
|
474
|
+
|
|
475
|
+
Duration: {self.results["total_duration"]:.2f} seconds
|
|
476
|
+
|
|
477
|
+
Results:
|
|
478
|
+
✓ Passed: {self.results["passed"]}
|
|
479
|
+
✗ Failed: {self.results["failed"]}
|
|
480
|
+
○ Skipped: {self.results["skipped"]}
|
|
481
|
+
|
|
482
|
+
Status: {"PASSED" if self.results["failed"] == 0 else "FAILED"}
|
|
483
|
+
"""
|
|
484
|
+
return report
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def main() -> None:
|
|
488
|
+
"""
|
|
489
|
+
CLI entry point.
|
|
490
|
+
|
|
491
|
+
Raises:
|
|
492
|
+
ValidationError: If arguments invalid
|
|
493
|
+
WorkItemNotFoundError: If work item not found
|
|
494
|
+
Various exceptions from runner methods
|
|
495
|
+
"""
|
|
496
|
+
from solokit.core.exceptions import WorkItemNotFoundError
|
|
497
|
+
from solokit.core.file_ops import load_json
|
|
498
|
+
|
|
499
|
+
if len(sys.argv) < 2:
|
|
500
|
+
raise ValidationError(
|
|
501
|
+
message="Missing required argument: work_item_id",
|
|
502
|
+
context={"usage": "python integration_test_runner.py <work_item_id>"},
|
|
503
|
+
remediation="Provide work item ID as command-line argument",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
work_item_id = sys.argv[1]
|
|
507
|
+
|
|
508
|
+
# Load work item
|
|
509
|
+
work_items_file = Path(".session/tracking/work_items.json")
|
|
510
|
+
data = load_json(work_items_file)
|
|
511
|
+
work_item = data["work_items"].get(work_item_id)
|
|
512
|
+
|
|
513
|
+
if not work_item:
|
|
514
|
+
raise WorkItemNotFoundError(work_item_id)
|
|
515
|
+
|
|
516
|
+
# Run integration tests
|
|
517
|
+
runner = IntegrationTestRunner(work_item)
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
# Setup
|
|
521
|
+
runner.setup_environment()
|
|
522
|
+
|
|
523
|
+
# Execute tests
|
|
524
|
+
results = runner.run_tests()
|
|
525
|
+
|
|
526
|
+
# Print report
|
|
527
|
+
output.info(runner.generate_report())
|
|
528
|
+
|
|
529
|
+
# Teardown
|
|
530
|
+
runner.teardown_environment()
|
|
531
|
+
|
|
532
|
+
# Exit with code 1 if tests failed
|
|
533
|
+
if results.get("failed", 0) > 0:
|
|
534
|
+
sys.exit(1)
|
|
535
|
+
else:
|
|
536
|
+
sys.exit(0)
|
|
537
|
+
|
|
538
|
+
except Exception:
|
|
539
|
+
# Attempt teardown on any failure
|
|
540
|
+
try:
|
|
541
|
+
runner.teardown_environment()
|
|
542
|
+
except Exception as teardown_error:
|
|
543
|
+
logger.warning(f"Teardown failed: {teardown_error}")
|
|
544
|
+
|
|
545
|
+
# Re-raise the original exception for proper error handling
|
|
546
|
+
raise
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
if __name__ == "__main__":
|
|
550
|
+
main()
|