charisma-cli 0.1.2__tar.gz
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.
- charisma_cli-0.1.2/.gitignore +134 -0
- charisma_cli-0.1.2/PKG-INFO +98 -0
- charisma_cli-0.1.2/README.md +73 -0
- charisma_cli-0.1.2/pyproject.toml +54 -0
- charisma_cli-0.1.2/src/charisma_cli/__init__.py +3 -0
- charisma_cli-0.1.2/src/charisma_cli/config.py +86 -0
- charisma_cli-0.1.2/src/charisma_cli/main.py +206 -0
- charisma_cli-0.1.2/src/charisma_cli/models.py +76 -0
- charisma_cli-0.1.2/src/charisma_cli/parser.py +175 -0
- charisma_cli-0.1.2/src/charisma_cli/retry.py +18 -0
- charisma_cli-0.1.2/src/charisma_cli/subprocess_mgr.py +63 -0
- charisma_cli-0.1.2/src/charisma_cli/uploader.py +488 -0
- charisma_cli-0.1.2/src/charisma_cli/watcher.py +223 -0
- charisma_cli-0.1.2/tests/__init__.py +1 -0
- charisma_cli-0.1.2/tests/conftest.py +137 -0
- charisma_cli-0.1.2/tests/test_cli.py +33 -0
- charisma_cli-0.1.2/tests/test_config.py +206 -0
- charisma_cli-0.1.2/tests/test_integration.py +172 -0
- charisma_cli-0.1.2/tests/test_models.py +246 -0
- charisma_cli-0.1.2/tests/test_parser.py +536 -0
- charisma_cli-0.1.2/tests/test_retry.py +83 -0
- charisma_cli-0.1.2/tests/test_silent_mode.py +217 -0
- charisma_cli-0.1.2/tests/test_subprocess_mgr.py +74 -0
- charisma_cli-0.1.2/tests/test_uploader.py +284 -0
- charisma_cli-0.1.2/tests/test_watcher.py +571 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
npm-debug.log*
|
|
4
|
+
yarn-debug.log*
|
|
5
|
+
yarn-error.log*
|
|
6
|
+
|
|
7
|
+
# Environment variables
|
|
8
|
+
.env
|
|
9
|
+
.env.local
|
|
10
|
+
.env.*.local
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/*
|
|
14
|
+
!.vscode/settings.json
|
|
15
|
+
!.vscode/tasks.json
|
|
16
|
+
!.vscode/launch.json
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
.idea/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
*~
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Build outputs
|
|
28
|
+
dist/
|
|
29
|
+
build/
|
|
30
|
+
*.log
|
|
31
|
+
|
|
32
|
+
# Local pipeline secrets (do not commit)
|
|
33
|
+
.env.pipeline
|
|
34
|
+
|
|
35
|
+
# Database
|
|
36
|
+
*.sqlite
|
|
37
|
+
*.db
|
|
38
|
+
|
|
39
|
+
# Test coverage
|
|
40
|
+
coverage/
|
|
41
|
+
.nyc_output/
|
|
42
|
+
|
|
43
|
+
# Temporary files
|
|
44
|
+
tmp/
|
|
45
|
+
temp/
|
|
46
|
+
*.tmp
|
|
47
|
+
|
|
48
|
+
# Confluence sync metadata (optional - can be committed)
|
|
49
|
+
# .amazonq/knowledge-base/.confluence-sync.json
|
|
50
|
+
|
|
51
|
+
# Nuxt build outputs
|
|
52
|
+
.nuxt/
|
|
53
|
+
.output/
|
|
54
|
+
dist/
|
|
55
|
+
|
|
56
|
+
# Python cache
|
|
57
|
+
*.pyc
|
|
58
|
+
*/__pycache__/
|
|
59
|
+
|
|
60
|
+
# Python virtual environments
|
|
61
|
+
.venv/
|
|
62
|
+
venv/
|
|
63
|
+
|
|
64
|
+
# Hypothesys cache
|
|
65
|
+
.hypothesis/
|
|
66
|
+
|
|
67
|
+
# Kiro hooks (auto-generated)
|
|
68
|
+
.kiro/hooks/
|
|
69
|
+
.kiro/settings/*
|
|
70
|
+
.kiro/settings/mcp.json
|
|
71
|
+
|
|
72
|
+
# Kiro spec config (local)
|
|
73
|
+
.kiro/specs/**/.config.kiro
|
|
74
|
+
|
|
75
|
+
# Coverage
|
|
76
|
+
.coverage
|
|
77
|
+
|
|
78
|
+
# AWS SAM build artifacts
|
|
79
|
+
.aws-sam/
|
|
80
|
+
|
|
81
|
+
# Zip files in repo root (test artifacts, allure exports, etc.)
|
|
82
|
+
*.zip
|
|
83
|
+
|
|
84
|
+
# Playwright test results
|
|
85
|
+
frontend/test-results/
|
|
86
|
+
frontend/playwright-report/
|
|
87
|
+
frontend/e2e-results/
|
|
88
|
+
test-results/
|
|
89
|
+
|
|
90
|
+
# E2E auth state (contains secrets)
|
|
91
|
+
frontend/e2e/.auth/
|
|
92
|
+
|
|
93
|
+
# E2E test environment (contains secrets)
|
|
94
|
+
frontend/.env.e2e
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
test-reporting.code-workspace
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
.vscode/extensions.json
|
|
101
|
+
Aurora.session.sql
|
|
102
|
+
Charisma DB.session.sql
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# API integration test setup scripts (contains sensitive token generation)
|
|
106
|
+
backend/api_integration_test_setup_scripts/
|
|
107
|
+
|
|
108
|
+
# Local test/debug scripts
|
|
109
|
+
scripts/test-ingestion-token.py
|
|
110
|
+
|
|
111
|
+
# vs code settings
|
|
112
|
+
.vscode/settings.json
|
|
113
|
+
backend/integration_test_report.xml
|
|
114
|
+
backend/unit_test_report.xml
|
|
115
|
+
frontend/app/pages/proto.vue
|
|
116
|
+
scripts/connect-aurora.ps1
|
|
117
|
+
|
|
118
|
+
# mock test result files
|
|
119
|
+
test-data-junit-debug*
|
|
120
|
+
|
|
121
|
+
# scripts for creating mock data
|
|
122
|
+
scripts/ingest-test-run/*
|
|
123
|
+
|
|
124
|
+
# --- ltm-power ---
|
|
125
|
+
ltm/*
|
|
126
|
+
ltm/store/*.jsonl
|
|
127
|
+
ltm/runtime/*
|
|
128
|
+
ltm/reports/*
|
|
129
|
+
ltm/snapshots/*
|
|
130
|
+
.kiro/steering/ltm-memory-format.md
|
|
131
|
+
.kiro/steering/ltm-operations.md
|
|
132
|
+
backend/coverage.json
|
|
133
|
+
*/__pycache__/*
|
|
134
|
+
allure-results/*
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: charisma-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: CLI tool that watches allure-results and streams test results + attachments to Charisma.
|
|
5
|
+
Author: Charisma Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: click<9.0,>=8.0
|
|
18
|
+
Requires-Dist: httpx<1.0,>=0.24
|
|
19
|
+
Requires-Dist: watchdog<5.0,>=3.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: hypothesis; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest<10.0,>=7.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: respx; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# charisma-cli
|
|
27
|
+
|
|
28
|
+
CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install charisma-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
|
|
37
|
+
|
|
38
|
+
For local development from a checkout of the repo:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install -e packages/charisma-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary>Alternative: install from the GitHub Packages registry (PyOCI)</summary>
|
|
46
|
+
|
|
47
|
+
Also published to GitHub Packages via [PyOCI](https://pyoci.com) (requires a GitHub token with `read:packages`) — useful for a build not yet on PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install charisma-cli \
|
|
51
|
+
--index-url "https://__token__:$GITHUB_TOKEN@pyoci.com/ghcr.io/align-QCOE/"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
</details>
|
|
55
|
+
|
|
56
|
+
## Publishing (maintainers)
|
|
57
|
+
|
|
58
|
+
Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
|
|
59
|
+
|
|
60
|
+
> Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
|
|
61
|
+
|
|
62
|
+
One-time setup (done once by a PyPI project owner):
|
|
63
|
+
|
|
64
|
+
1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
|
|
65
|
+
2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
|
|
66
|
+
3. Copy the token (starts with `pypi-`).
|
|
67
|
+
4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
|
|
68
|
+
5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
|
|
69
|
+
|
|
70
|
+
Rotate the token periodically and store it in the team secret manager.
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export CHARISMA_ENDPOINT=https://charisma.company.com
|
|
76
|
+
export CHARISMA_TOKEN=<your-service-token>
|
|
77
|
+
export CHARISMA_PROJECT_ID=my-project
|
|
78
|
+
|
|
79
|
+
charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Environment Variables
|
|
83
|
+
|
|
84
|
+
| Variable | Required | Description |
|
|
85
|
+
| --------------------- | -------- | -------------------------------------------------- |
|
|
86
|
+
| `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
|
|
87
|
+
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
88
|
+
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
89
|
+
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
90
|
+
|
|
91
|
+
## Flags
|
|
92
|
+
|
|
93
|
+
| Flag | Description |
|
|
94
|
+
| ---------------- | -------------------------------------------- |
|
|
95
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
96
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
97
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
98
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# charisma-cli
|
|
2
|
+
|
|
3
|
+
CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install charisma-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
|
|
12
|
+
|
|
13
|
+
For local development from a checkout of the repo:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e packages/charisma-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary>Alternative: install from the GitHub Packages registry (PyOCI)</summary>
|
|
21
|
+
|
|
22
|
+
Also published to GitHub Packages via [PyOCI](https://pyoci.com) (requires a GitHub token with `read:packages`) — useful for a build not yet on PyPI:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install charisma-cli \
|
|
26
|
+
--index-url "https://__token__:$GITHUB_TOKEN@pyoci.com/ghcr.io/align-QCOE/"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
</details>
|
|
30
|
+
|
|
31
|
+
## Publishing (maintainers)
|
|
32
|
+
|
|
33
|
+
Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
|
|
34
|
+
|
|
35
|
+
> Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
|
|
36
|
+
|
|
37
|
+
One-time setup (done once by a PyPI project owner):
|
|
38
|
+
|
|
39
|
+
1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
|
|
40
|
+
2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
|
|
41
|
+
3. Copy the token (starts with `pypi-`).
|
|
42
|
+
4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
|
|
43
|
+
5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
|
|
44
|
+
|
|
45
|
+
Rotate the token periodically and store it in the team secret manager.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export CHARISMA_ENDPOINT=https://charisma.company.com
|
|
51
|
+
export CHARISMA_TOKEN=<your-service-token>
|
|
52
|
+
export CHARISMA_PROJECT_ID=my-project
|
|
53
|
+
|
|
54
|
+
charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Environment Variables
|
|
58
|
+
|
|
59
|
+
| Variable | Required | Description |
|
|
60
|
+
| --------------------- | -------- | -------------------------------------------------- |
|
|
61
|
+
| `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
|
|
62
|
+
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
63
|
+
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
64
|
+
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
65
|
+
|
|
66
|
+
## Flags
|
|
67
|
+
|
|
68
|
+
| Flag | Description |
|
|
69
|
+
| ---------------- | -------------------------------------------- |
|
|
70
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
71
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
72
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
73
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Build: hatchling
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["hatchling"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "charisma-cli"
|
|
8
|
+
version = "0.1.2"
|
|
9
|
+
description = "CLI tool that watches allure-results and streams test results + attachments to Charisma."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Charisma Team" },
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Testing",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"click>=8.0,<9.0",
|
|
29
|
+
"httpx>=0.24,<1.0",
|
|
30
|
+
"watchdog>=3.0,<5.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
charismactl = "charisma_cli.main:cli"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=7.0,<10.0",
|
|
39
|
+
"respx",
|
|
40
|
+
"hypothesis",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/charisma_cli"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 120
|
|
51
|
+
target-version = "py310"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "W", "I"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Configuration dataclass with env var + CLI flag resolution."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Config:
|
|
11
|
+
"""Resolved configuration for charismactl.
|
|
12
|
+
|
|
13
|
+
Fields are populated via from_env() which merges CLI flag overrides
|
|
14
|
+
with environment variable fallbacks.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
endpoint: str
|
|
18
|
+
token: str
|
|
19
|
+
project: str
|
|
20
|
+
results_dir: str
|
|
21
|
+
silent: bool
|
|
22
|
+
skip_too_big: bool
|
|
23
|
+
drain_timeout: int
|
|
24
|
+
build_id: str | None
|
|
25
|
+
commit_sha: str | None
|
|
26
|
+
branch: str | None
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_env(
|
|
30
|
+
cls,
|
|
31
|
+
*,
|
|
32
|
+
endpoint: str | None = None,
|
|
33
|
+
token: str | None = None,
|
|
34
|
+
project: str | None = None,
|
|
35
|
+
results: str | None = None,
|
|
36
|
+
silent: bool = False,
|
|
37
|
+
skip_too_big: bool = False,
|
|
38
|
+
drain_timeout: int = 30,
|
|
39
|
+
) -> "Config":
|
|
40
|
+
"""Build Config by resolving CLI flags over environment variables.
|
|
41
|
+
|
|
42
|
+
Resolution order: CLI flag (if not None) > env var > default.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
endpoint: --endpoint flag override
|
|
46
|
+
token: --token flag override
|
|
47
|
+
project: --project flag override
|
|
48
|
+
results: --results flag override
|
|
49
|
+
silent: --silent flag
|
|
50
|
+
skip_too_big: --skip-too-big flag
|
|
51
|
+
drain_timeout: drain timeout in seconds
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Fully resolved Config instance.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
click.UsageError: If required fields (endpoint, token) are missing.
|
|
58
|
+
"""
|
|
59
|
+
resolved_endpoint = (endpoint or os.getenv("CHARISMA_ENDPOINT", "")).strip()
|
|
60
|
+
resolved_token = (token or os.getenv("CHARISMA_TOKEN", "")).strip()
|
|
61
|
+
resolved_project = (project or os.getenv("CHARISMA_PROJECT_ID", "")).strip()
|
|
62
|
+
resolved_results = results or os.getenv("CHARISMA_RESULTS", "allure-results")
|
|
63
|
+
|
|
64
|
+
if not resolved_endpoint:
|
|
65
|
+
raise click.UsageError(
|
|
66
|
+
"Missing required configuration: endpoint. "
|
|
67
|
+
"Set CHARISMA_ENDPOINT or pass --endpoint."
|
|
68
|
+
)
|
|
69
|
+
if not resolved_token:
|
|
70
|
+
raise click.UsageError(
|
|
71
|
+
"Missing required configuration: token. "
|
|
72
|
+
"Set CHARISMA_TOKEN or pass --token."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return cls(
|
|
76
|
+
endpoint=resolved_endpoint,
|
|
77
|
+
token=resolved_token,
|
|
78
|
+
project=resolved_project,
|
|
79
|
+
results_dir=resolved_results,
|
|
80
|
+
silent=silent,
|
|
81
|
+
skip_too_big=skip_too_big,
|
|
82
|
+
drain_timeout=drain_timeout,
|
|
83
|
+
build_id=os.getenv("BUILD_ID"),
|
|
84
|
+
commit_sha=os.getenv("COMMIT_SHA"),
|
|
85
|
+
branch=os.getenv("BRANCH"),
|
|
86
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""CLI entry point for charismactl."""
|
|
2
|
+
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from queue import PriorityQueue
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from charisma_cli import __version__
|
|
11
|
+
from charisma_cli.config import Config
|
|
12
|
+
from charisma_cli.models import FileEvent
|
|
13
|
+
from charisma_cli.subprocess_mgr import SubprocessManager
|
|
14
|
+
from charisma_cli.uploader import Uploader
|
|
15
|
+
from charisma_cli.watcher import ResultsWatcher, classify_file
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
@click.version_option(version=__version__, prog_name="charismactl")
|
|
20
|
+
def cli() -> None:
|
|
21
|
+
"""Stream allure test results to Charisma in real time."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _resolve_config(
|
|
25
|
+
endpoint: str | None,
|
|
26
|
+
token: str | None,
|
|
27
|
+
project: str,
|
|
28
|
+
results: str,
|
|
29
|
+
silent: bool,
|
|
30
|
+
skip_too_big: bool,
|
|
31
|
+
) -> Config:
|
|
32
|
+
"""Resolve CLI config from flags + env vars. Re-raises UsageError on failure."""
|
|
33
|
+
return Config.from_env(
|
|
34
|
+
endpoint=endpoint,
|
|
35
|
+
token=token,
|
|
36
|
+
project=project,
|
|
37
|
+
results=results,
|
|
38
|
+
silent=silent,
|
|
39
|
+
skip_too_big=skip_too_big,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _open_launch_or_exit(uploader: Uploader, config: Config) -> str:
|
|
44
|
+
"""Open a launch, handling failure based on silent mode.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The launch ID on success.
|
|
48
|
+
|
|
49
|
+
Exits:
|
|
50
|
+
sys.exit(1) if non-silent and launch open fails.
|
|
51
|
+
Returns empty string if silent and launch fails (caller must handle).
|
|
52
|
+
"""
|
|
53
|
+
launch_id = uploader.open_launch()
|
|
54
|
+
if launch_id is None:
|
|
55
|
+
if config.silent:
|
|
56
|
+
return ""
|
|
57
|
+
click.echo("Error: Failed to open launch. Exiting.", err=True)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
return launch_id
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _print_summary(uploader: Uploader) -> None:
|
|
63
|
+
"""Print the upload summary to stdout."""
|
|
64
|
+
summary = uploader.summary
|
|
65
|
+
click.echo(
|
|
66
|
+
f"Summary: results_sent={summary.results_sent}, "
|
|
67
|
+
f"results_failed={summary.results_failed}, "
|
|
68
|
+
f"attachments_sent={summary.attachments_sent}, "
|
|
69
|
+
f"attachments_skipped={summary.attachments_skipped}, "
|
|
70
|
+
f"containers_sent={summary.containers_sent}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@cli.command()
|
|
75
|
+
@click.option("--endpoint", envvar="CHARISMA_ENDPOINT", default=None, help="Charisma API endpoint.")
|
|
76
|
+
@click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
|
|
77
|
+
@click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
|
|
78
|
+
@click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
|
|
79
|
+
@click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
|
|
80
|
+
@click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
|
|
81
|
+
@click.argument("command", nargs=-1, required=True)
|
|
82
|
+
def watch(
|
|
83
|
+
endpoint: str | None,
|
|
84
|
+
token: str | None,
|
|
85
|
+
results: str,
|
|
86
|
+
project: str,
|
|
87
|
+
silent: bool,
|
|
88
|
+
skip_too_big: bool,
|
|
89
|
+
command: tuple[str, ...],
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Watch allure-results and stream to Charisma while running COMMAND.
|
|
92
|
+
|
|
93
|
+
Usage: charismactl watch --project my-project -- pytest -n 4 tests/
|
|
94
|
+
"""
|
|
95
|
+
config = _resolve_config(endpoint, token, project, results, silent, skip_too_big)
|
|
96
|
+
|
|
97
|
+
queue: PriorityQueue[FileEvent] = PriorityQueue()
|
|
98
|
+
uploader = Uploader(config, queue)
|
|
99
|
+
uploader.configure_logging()
|
|
100
|
+
|
|
101
|
+
# Open launch (Uploader owns the client)
|
|
102
|
+
launch_id = _open_launch_or_exit(uploader, config)
|
|
103
|
+
if not launch_id:
|
|
104
|
+
# Silent degradation — run subprocess without uploads
|
|
105
|
+
mgr = SubprocessManager()
|
|
106
|
+
mgr.spawn(command)
|
|
107
|
+
sys.exit(mgr.wait())
|
|
108
|
+
|
|
109
|
+
# Start watcher + uploader consumer
|
|
110
|
+
watcher = ResultsWatcher(config.results_dir, queue, config)
|
|
111
|
+
watcher.start()
|
|
112
|
+
uploader.start()
|
|
113
|
+
|
|
114
|
+
# Spawn subprocess with signal forwarding
|
|
115
|
+
mgr = SubprocessManager()
|
|
116
|
+
|
|
117
|
+
def _forward_signal(sig: int, _frame: object) -> None:
|
|
118
|
+
mgr.forward_signal(sig)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
signal.signal(signal.SIGTERM, _forward_signal)
|
|
122
|
+
except (OSError, ValueError):
|
|
123
|
+
pass
|
|
124
|
+
try:
|
|
125
|
+
signal.signal(signal.SIGINT, _forward_signal)
|
|
126
|
+
except (OSError, ValueError):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
mgr.spawn(command)
|
|
130
|
+
exit_code = mgr.wait()
|
|
131
|
+
|
|
132
|
+
# After subprocess exits (crash or normal): flush debouncing files and scan
|
|
133
|
+
# for anything the watcher missed entirely. This prevents data loss when
|
|
134
|
+
# parallel test workers write results in bursts just before crash.
|
|
135
|
+
watcher.flush_pending()
|
|
136
|
+
watcher.final_scan()
|
|
137
|
+
|
|
138
|
+
# Drain and close — stop watcher AFTER drain so all files are processed
|
|
139
|
+
uploader.drain(timeout=config.drain_timeout)
|
|
140
|
+
watcher.stop()
|
|
141
|
+
uploader.stop()
|
|
142
|
+
|
|
143
|
+
uploader.ensure_client()
|
|
144
|
+
uploader.close_launch()
|
|
145
|
+
if uploader._client is not None:
|
|
146
|
+
uploader._client.close()
|
|
147
|
+
uploader._client = None
|
|
148
|
+
|
|
149
|
+
_print_summary(uploader)
|
|
150
|
+
sys.exit(exit_code)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@cli.command()
|
|
154
|
+
@click.option("--endpoint", envvar="CHARISMA_ENDPOINT", default=None, help="Charisma API endpoint.")
|
|
155
|
+
@click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
|
|
156
|
+
@click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
|
|
157
|
+
@click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
|
|
158
|
+
@click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
|
|
159
|
+
@click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
|
|
160
|
+
def upload(
|
|
161
|
+
endpoint: str | None,
|
|
162
|
+
token: str | None,
|
|
163
|
+
results: str,
|
|
164
|
+
project: str,
|
|
165
|
+
silent: bool,
|
|
166
|
+
skip_too_big: bool,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Upload completed allure-results to Charisma (post-execution).
|
|
169
|
+
|
|
170
|
+
Usage: charismactl upload --project my-project --results allure-results
|
|
171
|
+
"""
|
|
172
|
+
config = _resolve_config(endpoint, token, project, results, silent, skip_too_big)
|
|
173
|
+
|
|
174
|
+
queue: PriorityQueue[FileEvent] = PriorityQueue()
|
|
175
|
+
uploader = Uploader(config, queue)
|
|
176
|
+
uploader.configure_logging()
|
|
177
|
+
|
|
178
|
+
# Open launch
|
|
179
|
+
launch_id = _open_launch_or_exit(uploader, config)
|
|
180
|
+
if not launch_id:
|
|
181
|
+
click.echo("Warning: Failed to open launch. No results uploaded.", err=True)
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
# Scan existing results directory
|
|
185
|
+
results_path = Path(config.results_dir)
|
|
186
|
+
if not results_path.exists():
|
|
187
|
+
click.echo(f"Error: Results directory '{config.results_dir}' does not exist.", err=True)
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
|
|
190
|
+
for filepath in sorted(results_path.iterdir()):
|
|
191
|
+
if filepath.is_file():
|
|
192
|
+
category = classify_file(filepath.name)
|
|
193
|
+
if category is not None:
|
|
194
|
+
queue.put(FileEvent(path=filepath, category=category))
|
|
195
|
+
|
|
196
|
+
# Process and drain
|
|
197
|
+
uploader.start()
|
|
198
|
+
uploader.drain(timeout=config.drain_timeout)
|
|
199
|
+
uploader.stop()
|
|
200
|
+
|
|
201
|
+
# Close launch
|
|
202
|
+
uploader.ensure_client()
|
|
203
|
+
uploader.close_launch()
|
|
204
|
+
uploader.stop()
|
|
205
|
+
|
|
206
|
+
_print_summary(uploader)
|