charisma-cli 0.3.0__tar.gz → 0.4.0__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.3.0 → charisma_cli-0.4.0}/.gitignore +16 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/PKG-INFO +23 -7
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/README.md +22 -6
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/pyproject.toml +1 -1
- charisma_cli-0.4.0/src/charisma_cli/__init__.py +7 -0
- charisma_cli-0.4.0/src/charisma_cli/config.py +163 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/main.py +126 -37
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/uploader.py +93 -11
- charisma_cli-0.4.0/tests/test_bamboo_labels.py +77 -0
- charisma_cli-0.4.0/tests/test_cli.py +186 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_config.py +132 -0
- charisma_cli-0.4.0/tests/test_open_launch_payload.py +152 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_uploader.py +143 -0
- charisma_cli-0.3.0/src/charisma_cli/__init__.py +0 -3
- charisma_cli-0.3.0/src/charisma_cli/config.py +0 -91
- charisma_cli-0.3.0/tests/test_cli.py +0 -49
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/launch_url.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/models.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/parser.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/retry.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/subprocess_mgr.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/src/charisma_cli/watcher.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/__init__.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/conftest.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_integration.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_launch_url.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_models.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_parser.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_retry.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_silent_mode.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_subprocess_mgr.py +0 -0
- {charisma_cli-0.3.0 → charisma_cli-0.4.0}/tests/test_watcher.py +0 -0
|
@@ -65,6 +65,10 @@ venv/
|
|
|
65
65
|
# Hypothesys cache
|
|
66
66
|
.hypothesis/
|
|
67
67
|
|
|
68
|
+
# mypy incremental cache
|
|
69
|
+
.mypy_cache/
|
|
70
|
+
backend/.mypy_cache/
|
|
71
|
+
|
|
68
72
|
# Kiro hooks (auto-generated)
|
|
69
73
|
.kiro/hooks/
|
|
70
74
|
.kiro/settings/*
|
|
@@ -109,6 +113,9 @@ backend/api_integration_test_setup_scripts/
|
|
|
109
113
|
# Local test/debug scripts
|
|
110
114
|
scripts/test-ingestion-token.py
|
|
111
115
|
|
|
116
|
+
# Local-only migration verification test (run manually when migration 044 is applied; not part of the suite)
|
|
117
|
+
backend/tests/integration/test_backfill_variant_history_id_migration_local.py
|
|
118
|
+
|
|
112
119
|
# vs code settings
|
|
113
120
|
.vscode/settings.json
|
|
114
121
|
backend/integration_test_report.xml
|
|
@@ -136,3 +143,12 @@ allure-results/*
|
|
|
136
143
|
allure-results-live/
|
|
137
144
|
*tasks.meta.json
|
|
138
145
|
*.code-workspace
|
|
146
|
+
|
|
147
|
+
# Session-specific work-state tracking (see .kiro update-work-state skill)
|
|
148
|
+
.current-task.md
|
|
149
|
+
|
|
150
|
+
# Local agent verdicts, RCAs, and pipeline state — never tracked in the repo
|
|
151
|
+
.agents/
|
|
152
|
+
|
|
153
|
+
# sql files for locally run DB queries
|
|
154
|
+
/*.sql
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: charisma-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI tool that watches allure-results and streams test results + attachments to Charisma.
|
|
5
5
|
Author: Charisma Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -75,15 +75,31 @@ charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
|
75
75
|
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
76
76
|
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
77
77
|
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
78
|
+
| `CHARISMA_RUN_KEY` | No | Run identity / merge key (`--run-key` fallback) |
|
|
79
|
+
| `BUILD_ID` | No | CI build identifier (`--build-id` fallback; `CHARISMA_BUILD_ID` also honored) |
|
|
80
|
+
| `BUILD_PLAN_KEY` | No | Build plan key (`--build-plan-key` fallback; `CHARISMA_BUILD_PLAN_KEY` also honored) |
|
|
81
|
+
| `ENVIRONMENT` | No | Environment code (`--environment` fallback; `CHARISMA_ENVIRONMENT` also honored) |
|
|
82
|
+
| `COMPONENT` | No | Component alias (`--component` fallback; `CHARISMA_COMPONENT` also honored) |
|
|
83
|
+
| `SOURCE_URL` | No | CI/CD build link (`--source-url` fallback; `CHARISMA_SOURCE_URL` also honored) |
|
|
78
84
|
|
|
79
85
|
## Flags
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
| `--
|
|
86
|
-
| `--
|
|
87
|
+
Both `upload` and `watch` accept the identical option set.
|
|
88
|
+
|
|
89
|
+
| Flag | Description |
|
|
90
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
91
|
+
| `--endpoint URL` | Charisma API endpoint (overrides `CHARISMA_ENDPOINT`) |
|
|
92
|
+
| `--token TOKEN` | Bearer token (overrides `CHARISMA_TOKEN`) |
|
|
93
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
94
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
95
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
96
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
97
|
+
| `--component ALIAS` | Component alias within the project |
|
|
98
|
+
| `--source-url URL` | CI/CD build link for this run |
|
|
99
|
+
| `--build-plan-key K` | Explicit build plan key for resolution (forwarded as `buildPlanKey`) |
|
|
100
|
+
| `--build-id ID` | CI build identifier for this run (forwarded as `buildId`) |
|
|
101
|
+
| `--environment ENV` | Environment code (e.g. `qat`, `prod`), stored on the launch |
|
|
102
|
+
| `--run-key KEY` | Run identity / merge key (reruns + parallel workers share it) |
|
|
87
103
|
|
|
88
104
|
## Launch URL in CI
|
|
89
105
|
|
|
@@ -50,15 +50,31 @@ charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
|
50
50
|
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
51
51
|
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
52
52
|
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
53
|
+
| `CHARISMA_RUN_KEY` | No | Run identity / merge key (`--run-key` fallback) |
|
|
54
|
+
| `BUILD_ID` | No | CI build identifier (`--build-id` fallback; `CHARISMA_BUILD_ID` also honored) |
|
|
55
|
+
| `BUILD_PLAN_KEY` | No | Build plan key (`--build-plan-key` fallback; `CHARISMA_BUILD_PLAN_KEY` also honored) |
|
|
56
|
+
| `ENVIRONMENT` | No | Environment code (`--environment` fallback; `CHARISMA_ENVIRONMENT` also honored) |
|
|
57
|
+
| `COMPONENT` | No | Component alias (`--component` fallback; `CHARISMA_COMPONENT` also honored) |
|
|
58
|
+
| `SOURCE_URL` | No | CI/CD build link (`--source-url` fallback; `CHARISMA_SOURCE_URL` also honored) |
|
|
53
59
|
|
|
54
60
|
## Flags
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
| `--
|
|
61
|
-
| `--
|
|
62
|
+
Both `upload` and `watch` accept the identical option set.
|
|
63
|
+
|
|
64
|
+
| Flag | Description |
|
|
65
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
66
|
+
| `--endpoint URL` | Charisma API endpoint (overrides `CHARISMA_ENDPOINT`) |
|
|
67
|
+
| `--token TOKEN` | Bearer token (overrides `CHARISMA_TOKEN`) |
|
|
68
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
69
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
70
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
71
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
72
|
+
| `--component ALIAS` | Component alias within the project |
|
|
73
|
+
| `--source-url URL` | CI/CD build link for this run |
|
|
74
|
+
| `--build-plan-key K` | Explicit build plan key for resolution (forwarded as `buildPlanKey`) |
|
|
75
|
+
| `--build-id ID` | CI build identifier for this run (forwarded as `buildId`) |
|
|
76
|
+
| `--environment ENV` | Environment code (e.g. `qat`, `prod`), stored on the launch |
|
|
77
|
+
| `--run-key KEY` | Run identity / merge key (reruns + parallel workers share it) |
|
|
62
78
|
|
|
63
79
|
## Launch URL in CI
|
|
64
80
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""charisma-cli: Stream allure results to Charisma in real time."""
|
|
2
|
+
|
|
3
|
+
# Single source of truth for the CLI version. At build time the wheel metadata
|
|
4
|
+
# is generated from pyproject.toml [project].version, so keeping this literal
|
|
5
|
+
# equal to pyproject guarantees `--version` reports the published version. The
|
|
6
|
+
# prepare-commit step owns the PATCH bump — do not hand-bump here.
|
|
7
|
+
__version__ = "0.4.0"
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
def _resolve_env(bare_name: str) -> str | None:
|
|
10
|
+
"""Resolve an env var by its bare name, falling back to the CHARISMA_ prefix.
|
|
11
|
+
|
|
12
|
+
Bare-name-first (e.g. ``BUILD_ID``) keeps parity with CI systems that export
|
|
13
|
+
generic build metadata, while the ``CHARISMA_`` prefixed form (e.g.
|
|
14
|
+
``CHARISMA_BUILD_ID``) is honored as a namespaced fallback. Returns the first
|
|
15
|
+
non-empty value, or None.
|
|
16
|
+
"""
|
|
17
|
+
for name in (bare_name, f"CHARISMA_{bare_name}"):
|
|
18
|
+
value = os.getenv(name)
|
|
19
|
+
if value and value.strip():
|
|
20
|
+
return value.strip()
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Config:
|
|
26
|
+
"""Resolved configuration for charismactl.
|
|
27
|
+
|
|
28
|
+
Fields are populated via from_env() which merges CLI flag overrides
|
|
29
|
+
with environment variable fallbacks.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
endpoint: str
|
|
33
|
+
token: str
|
|
34
|
+
project: str
|
|
35
|
+
results_dir: str
|
|
36
|
+
silent: bool
|
|
37
|
+
skip_too_big: bool
|
|
38
|
+
drain_timeout: int
|
|
39
|
+
build_id: str | None
|
|
40
|
+
commit_sha: str | None
|
|
41
|
+
branch: str | None
|
|
42
|
+
component: str | None = None
|
|
43
|
+
source_url: str | None = None
|
|
44
|
+
build_plan_key: str | None = None
|
|
45
|
+
environment: str | None = None
|
|
46
|
+
name: str | None = None
|
|
47
|
+
tags: list[str] | None = None
|
|
48
|
+
run_key: str | None = None
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_env(
|
|
52
|
+
cls,
|
|
53
|
+
*,
|
|
54
|
+
endpoint: str | None = None,
|
|
55
|
+
token: str | None = None,
|
|
56
|
+
project: str | None = None,
|
|
57
|
+
results: str | None = None,
|
|
58
|
+
silent: bool = False,
|
|
59
|
+
skip_too_big: bool = False,
|
|
60
|
+
drain_timeout: int = 30,
|
|
61
|
+
component: str | None = None,
|
|
62
|
+
source_url: str | None = None,
|
|
63
|
+
build_plan_key: str | None = None,
|
|
64
|
+
build_id: str | None = None,
|
|
65
|
+
environment: str | None = None,
|
|
66
|
+
run_key: str | None = None,
|
|
67
|
+
) -> "Config":
|
|
68
|
+
"""Build Config by resolving CLI flags over environment variables.
|
|
69
|
+
|
|
70
|
+
Resolution order: CLI flag (if not None) > env var > default.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
endpoint: --endpoint flag override
|
|
74
|
+
token: --token flag override
|
|
75
|
+
project: --project flag override
|
|
76
|
+
results: --results flag override
|
|
77
|
+
silent: --silent flag
|
|
78
|
+
skip_too_big: --skip-too-big flag
|
|
79
|
+
drain_timeout: drain timeout in seconds
|
|
80
|
+
component: --component flag override (component alias within the
|
|
81
|
+
project). Env: COMPONENT, fallback CHARISMA_COMPONENT.
|
|
82
|
+
source_url: --source-url flag override (CI/CD build link). Env:
|
|
83
|
+
SOURCE_URL, fallback CHARISMA_SOURCE_URL.
|
|
84
|
+
build_plan_key: --build-plan-key flag override (explicit build plan
|
|
85
|
+
key). Env: BUILD_PLAN_KEY, fallback CHARISMA_BUILD_PLAN_KEY.
|
|
86
|
+
build_id: --build-id flag override (CI build identifier). Env:
|
|
87
|
+
BUILD_ID, fallback CHARISMA_BUILD_ID. Flag wins over env.
|
|
88
|
+
environment: --environment flag override (env code, e.g. "qat").
|
|
89
|
+
Persisted to the first-class launch environment column. Env:
|
|
90
|
+
ENVIRONMENT, fallback CHARISMA_ENVIRONMENT.
|
|
91
|
+
run_key: --run-key flag override. Client-supplied merge key: the
|
|
92
|
+
backend derives the launch id as uuid5(project/run_key) and
|
|
93
|
+
get-or-creates, so reruns and parallel producers sharing a key
|
|
94
|
+
land in one launch. Empty/whitespace resolves to None (a new
|
|
95
|
+
random launch, unchanged behavior).
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Fully resolved Config instance.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
click.UsageError: If required fields (endpoint, token) are missing.
|
|
102
|
+
"""
|
|
103
|
+
# Connection config resolves from the CHARISMA_-prefixed name ONLY (no
|
|
104
|
+
# bare form): a generic ENDPOINT/TOKEN/PROJECT_ID/RUN_KEY in the
|
|
105
|
+
# environment must not silently reconfigure the client. Build/run
|
|
106
|
+
# metadata below uses _resolve_env (bare-first, prefixed fallback) so it
|
|
107
|
+
# picks up generic CI variables. Do NOT "unify" these — the split is
|
|
108
|
+
# intentional and mirrors pytest-charisma (bare_env_var=None for config).
|
|
109
|
+
resolved_endpoint = (endpoint or os.getenv("CHARISMA_ENDPOINT", "")).strip()
|
|
110
|
+
resolved_token = (token or os.getenv("CHARISMA_TOKEN", "")).strip()
|
|
111
|
+
resolved_project = (project or os.getenv("CHARISMA_PROJECT_ID", "")).strip()
|
|
112
|
+
resolved_results = results or os.getenv("CHARISMA_RESULTS", "allure-results")
|
|
113
|
+
resolved_run_key = (run_key or os.getenv("CHARISMA_RUN_KEY", "")).strip() or None
|
|
114
|
+
resolved_component = (component or _resolve_env("COMPONENT") or "").strip() or None
|
|
115
|
+
resolved_source_url = (source_url or _resolve_env("SOURCE_URL") or "").strip() or None
|
|
116
|
+
resolved_build_plan_key = (build_plan_key or _resolve_env("BUILD_PLAN_KEY") or "").strip() or None
|
|
117
|
+
resolved_build_id = (build_id or _resolve_env("BUILD_ID") or "").strip() or None
|
|
118
|
+
resolved_environment = (environment or _resolve_env("ENVIRONMENT") or "").strip() or None
|
|
119
|
+
# Human-readable launch name (LAUNCH_NAME) and comma-separated tags
|
|
120
|
+
# (TAGS, e.g. "CI/CD,performance") shown on the Launches page.
|
|
121
|
+
resolved_name = (_resolve_env("LAUNCH_NAME") or "").strip() or None
|
|
122
|
+
resolved_tags = [t.strip() for t in (_resolve_env("TAGS") or "").split(",") if t.strip()] or None
|
|
123
|
+
|
|
124
|
+
# Merge Bamboo agent labels into resolved_tags (zero-config, opportunistic).
|
|
125
|
+
# bamboo_agentLabelString is a space-separated list of CI agent labels set
|
|
126
|
+
# automatically by Bamboo. Tokens are deduplicated against any explicit TAGS.
|
|
127
|
+
bamboo_raw = os.environ.get("bamboo_agentLabelString", "").strip()
|
|
128
|
+
if bamboo_raw:
|
|
129
|
+
base = list(resolved_tags or [])
|
|
130
|
+
seen: set[str] = set(base)
|
|
131
|
+
for label in bamboo_raw.split():
|
|
132
|
+
label = label.strip()
|
|
133
|
+
if label and label not in seen:
|
|
134
|
+
base.append(label)
|
|
135
|
+
seen.add(label)
|
|
136
|
+
resolved_tags = base or None
|
|
137
|
+
|
|
138
|
+
if not resolved_endpoint:
|
|
139
|
+
raise click.UsageError(
|
|
140
|
+
"Missing required configuration: endpoint. Set CHARISMA_ENDPOINT or pass --endpoint."
|
|
141
|
+
)
|
|
142
|
+
if not resolved_token:
|
|
143
|
+
raise click.UsageError("Missing required configuration: token. Set CHARISMA_TOKEN or pass --token.")
|
|
144
|
+
|
|
145
|
+
return cls(
|
|
146
|
+
endpoint=resolved_endpoint,
|
|
147
|
+
token=resolved_token,
|
|
148
|
+
project=resolved_project,
|
|
149
|
+
results_dir=resolved_results,
|
|
150
|
+
silent=silent,
|
|
151
|
+
skip_too_big=skip_too_big,
|
|
152
|
+
drain_timeout=drain_timeout,
|
|
153
|
+
build_id=resolved_build_id,
|
|
154
|
+
commit_sha=_resolve_env("COMMIT_SHA"),
|
|
155
|
+
branch=_resolve_env("BRANCH"),
|
|
156
|
+
component=resolved_component,
|
|
157
|
+
source_url=resolved_source_url,
|
|
158
|
+
build_plan_key=resolved_build_plan_key,
|
|
159
|
+
environment=resolved_environment,
|
|
160
|
+
name=resolved_name,
|
|
161
|
+
tags=resolved_tags,
|
|
162
|
+
run_key=resolved_run_key,
|
|
163
|
+
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import signal
|
|
4
4
|
import sys
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from queue import PriorityQueue
|
|
7
8
|
|
|
@@ -39,6 +40,11 @@ def _resolve_config(
|
|
|
39
40
|
results: str,
|
|
40
41
|
silent: bool,
|
|
41
42
|
skip_too_big: bool,
|
|
43
|
+
component: str | None = None,
|
|
44
|
+
source_url: str | None = None,
|
|
45
|
+
build_plan_key: str | None = None,
|
|
46
|
+
build_id: str | None = None,
|
|
47
|
+
environment: str | None = None,
|
|
42
48
|
run_key: str | None = None,
|
|
43
49
|
) -> Config:
|
|
44
50
|
"""Resolve CLI config from flags + env vars. Re-raises UsageError on failure."""
|
|
@@ -49,6 +55,11 @@ def _resolve_config(
|
|
|
49
55
|
results=results,
|
|
50
56
|
silent=silent,
|
|
51
57
|
skip_too_big=skip_too_big,
|
|
58
|
+
component=component,
|
|
59
|
+
source_url=source_url,
|
|
60
|
+
build_plan_key=build_plan_key,
|
|
61
|
+
build_id=build_id,
|
|
62
|
+
environment=environment,
|
|
52
63
|
run_key=run_key,
|
|
53
64
|
)
|
|
54
65
|
|
|
@@ -135,23 +146,50 @@ def _print_summary(uploader: Uploader) -> None:
|
|
|
135
146
|
)
|
|
136
147
|
|
|
137
148
|
|
|
149
|
+
def _common_options(func: Callable) -> Callable:
|
|
150
|
+
"""Apply the shared connection + launch-metadata options to a command.
|
|
151
|
+
|
|
152
|
+
`watch` and `upload` accept the identical option set; defining it once here
|
|
153
|
+
keeps them from drifting (a new launch field is added in one place, not two).
|
|
154
|
+
Decorators apply bottom-up, so the visible order on the command matches the
|
|
155
|
+
order below.
|
|
156
|
+
"""
|
|
157
|
+
options = [
|
|
158
|
+
click.option("--endpoint", envvar="CHARISMA_ENDPOINT", default=None, help="Charisma API endpoint."),
|
|
159
|
+
click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token."),
|
|
160
|
+
click.option(
|
|
161
|
+
"--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory."
|
|
162
|
+
),
|
|
163
|
+
click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias."),
|
|
164
|
+
click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur."),
|
|
165
|
+
click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB."),
|
|
166
|
+
click.option("--component", default=None, help="Component alias within the project."),
|
|
167
|
+
click.option("--source-url", default=None, help="CI/CD build link for this run."),
|
|
168
|
+
click.option("--build-plan-key", default=None, help="Explicit build plan key for resolution."),
|
|
169
|
+
click.option("--build-id", default=None, help="CI build identifier for this run (forwarded as buildId)."),
|
|
170
|
+
click.option(
|
|
171
|
+
"--environment",
|
|
172
|
+
default=None,
|
|
173
|
+
help="Environment code (e.g. 'qat', 'prod'). Stored as the launch environment.",
|
|
174
|
+
),
|
|
175
|
+
click.option(
|
|
176
|
+
"--run-key",
|
|
177
|
+
envvar="CHARISMA_RUN_KEY",
|
|
178
|
+
default=None,
|
|
179
|
+
help="Run identity / merge key (NOT the build id). Producers sharing this key "
|
|
180
|
+
"stream into one launch (launch id = uuid5(project/run-key)) — powers "
|
|
181
|
+
"rerun-in-place and parallel workers; reruns record retry attempts. Use your "
|
|
182
|
+
"CI run/pipeline id (e.g. GITHUB_RUN_ID) so retries merge but separate runs "
|
|
183
|
+
"stay separate. Omit for a new launch.",
|
|
184
|
+
),
|
|
185
|
+
]
|
|
186
|
+
for option in reversed(options):
|
|
187
|
+
func = option(func)
|
|
188
|
+
return func
|
|
189
|
+
|
|
190
|
+
|
|
138
191
|
@cli.command()
|
|
139
|
-
@
|
|
140
|
-
@click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
|
|
141
|
-
@click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
|
|
142
|
-
@click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
|
|
143
|
-
@click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
|
|
144
|
-
@click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
|
|
145
|
-
@click.option(
|
|
146
|
-
"--run-key",
|
|
147
|
-
envvar="CHARISMA_RUN_KEY",
|
|
148
|
-
default=None,
|
|
149
|
-
help="Run identity / merge key (NOT the build id). Producers sharing this key "
|
|
150
|
-
"stream into one launch (launch id = uuid5(project/run-key)) — powers "
|
|
151
|
-
"rerun-in-place and parallel workers; reruns record retry attempts. Use your "
|
|
152
|
-
"CI run/pipeline id (e.g. GITHUB_RUN_ID) so retries merge but separate runs "
|
|
153
|
-
"stay separate. Omit for a new launch.",
|
|
154
|
-
)
|
|
192
|
+
@_common_options
|
|
155
193
|
@click.argument("command", nargs=-1, required=True)
|
|
156
194
|
def watch(
|
|
157
195
|
endpoint: str | None,
|
|
@@ -160,6 +198,11 @@ def watch(
|
|
|
160
198
|
project: str,
|
|
161
199
|
silent: bool,
|
|
162
200
|
skip_too_big: bool,
|
|
201
|
+
component: str | None,
|
|
202
|
+
source_url: str | None,
|
|
203
|
+
build_plan_key: str | None,
|
|
204
|
+
build_id: str | None,
|
|
205
|
+
environment: str | None,
|
|
163
206
|
run_key: str | None,
|
|
164
207
|
command: tuple[str, ...],
|
|
165
208
|
) -> None:
|
|
@@ -167,7 +210,20 @@ def watch(
|
|
|
167
210
|
|
|
168
211
|
Usage: charismactl watch --project my-project -- pytest -n 4 tests/
|
|
169
212
|
"""
|
|
170
|
-
config = _resolve_config(
|
|
213
|
+
config = _resolve_config(
|
|
214
|
+
endpoint,
|
|
215
|
+
token,
|
|
216
|
+
project,
|
|
217
|
+
results,
|
|
218
|
+
silent,
|
|
219
|
+
skip_too_big,
|
|
220
|
+
component,
|
|
221
|
+
source_url,
|
|
222
|
+
build_plan_key,
|
|
223
|
+
build_id,
|
|
224
|
+
environment,
|
|
225
|
+
run_key,
|
|
226
|
+
)
|
|
171
227
|
|
|
172
228
|
queue: PriorityQueue[FileEvent] = PriorityQueue()
|
|
173
229
|
uploader = Uploader(config, queue)
|
|
@@ -220,7 +276,12 @@ def watch(
|
|
|
220
276
|
|
|
221
277
|
# Drain and close — stop the watcher AFTER drain so the consumer's final
|
|
222
278
|
# queue flush still sees anything the observer enqueued during drain.
|
|
223
|
-
|
|
279
|
+
# drain() is progress-based and returns False if it stalled or any send
|
|
280
|
+
# failed — same partial-send guard as `upload`. In watch mode a dropped
|
|
281
|
+
# upload must not be masked by a passing test run (exit 0), so an incomplete
|
|
282
|
+
# drain forces a non-zero exit while still preserving a failing test's own
|
|
283
|
+
# exit code.
|
|
284
|
+
fully_drained = uploader.drain(timeout=config.drain_timeout)
|
|
224
285
|
watcher.stop()
|
|
225
286
|
uploader.stop()
|
|
226
287
|
|
|
@@ -232,26 +293,22 @@ def watch(
|
|
|
232
293
|
|
|
233
294
|
_warn_if_results_unsent(uploader, config)
|
|
234
295
|
_print_summary(uploader)
|
|
296
|
+
|
|
297
|
+
if not fully_drained:
|
|
298
|
+
click.echo(
|
|
299
|
+
"Error: upload did not finish — the consumer stalled or results "
|
|
300
|
+
"failed to send; some results were dropped. Exiting non-zero so this "
|
|
301
|
+
"run is not treated as a successful upload.",
|
|
302
|
+
err=True,
|
|
303
|
+
)
|
|
304
|
+
# Preserve a failing test's exit code; otherwise surface the upload
|
|
305
|
+
# failure as exit 1 instead of letting a passing test mask it.
|
|
306
|
+
sys.exit(exit_code if exit_code != 0 else 1)
|
|
235
307
|
sys.exit(exit_code)
|
|
236
308
|
|
|
237
309
|
|
|
238
310
|
@cli.command()
|
|
239
|
-
@
|
|
240
|
-
@click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
|
|
241
|
-
@click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
|
|
242
|
-
@click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
|
|
243
|
-
@click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
|
|
244
|
-
@click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
|
|
245
|
-
@click.option(
|
|
246
|
-
"--run-key",
|
|
247
|
-
envvar="CHARISMA_RUN_KEY",
|
|
248
|
-
default=None,
|
|
249
|
-
help="Run identity / merge key (NOT the build id). Producers sharing this key "
|
|
250
|
-
"stream into one launch (launch id = uuid5(project/run-key)) — powers "
|
|
251
|
-
"rerun-in-place and parallel workers; reruns record retry attempts. Use your "
|
|
252
|
-
"CI run/pipeline id (e.g. GITHUB_RUN_ID) so retries merge but separate runs "
|
|
253
|
-
"stay separate. Omit for a new launch.",
|
|
254
|
-
)
|
|
311
|
+
@_common_options
|
|
255
312
|
def upload(
|
|
256
313
|
endpoint: str | None,
|
|
257
314
|
token: str | None,
|
|
@@ -259,13 +316,31 @@ def upload(
|
|
|
259
316
|
project: str,
|
|
260
317
|
silent: bool,
|
|
261
318
|
skip_too_big: bool,
|
|
319
|
+
component: str | None,
|
|
320
|
+
source_url: str | None,
|
|
321
|
+
build_plan_key: str | None,
|
|
322
|
+
build_id: str | None,
|
|
323
|
+
environment: str | None,
|
|
262
324
|
run_key: str | None,
|
|
263
325
|
) -> None:
|
|
264
326
|
"""Upload completed allure-results to Charisma (post-execution).
|
|
265
327
|
|
|
266
328
|
Usage: charismactl upload --project my-project --results allure-results
|
|
267
329
|
"""
|
|
268
|
-
config = _resolve_config(
|
|
330
|
+
config = _resolve_config(
|
|
331
|
+
endpoint,
|
|
332
|
+
token,
|
|
333
|
+
project,
|
|
334
|
+
results,
|
|
335
|
+
silent,
|
|
336
|
+
skip_too_big,
|
|
337
|
+
component,
|
|
338
|
+
source_url,
|
|
339
|
+
build_plan_key,
|
|
340
|
+
build_id,
|
|
341
|
+
environment,
|
|
342
|
+
run_key,
|
|
343
|
+
)
|
|
269
344
|
|
|
270
345
|
queue: PriorityQueue[FileEvent] = PriorityQueue()
|
|
271
346
|
uploader = Uploader(config, queue)
|
|
@@ -292,9 +367,14 @@ def upload(
|
|
|
292
367
|
if category is not None:
|
|
293
368
|
queue.put(FileEvent(path=filepath, category=category))
|
|
294
369
|
|
|
295
|
-
# Process and drain
|
|
370
|
+
# Process and drain. drain() is progress-based: it runs to completion for a
|
|
371
|
+
# healthy upload of any size and only returns False if the consumer STALLS
|
|
372
|
+
# (no progress for drain_timeout seconds). A False return means results were
|
|
373
|
+
# dropped — we must still close the launch and print the summary, then exit
|
|
374
|
+
# non-zero so CI sees a failure instead of a green build with a partial send
|
|
375
|
+
# (the upload results_sent=700/10158-queued bug).
|
|
296
376
|
uploader.start()
|
|
297
|
-
uploader.drain(timeout=config.drain_timeout)
|
|
377
|
+
fully_drained = uploader.drain(timeout=config.drain_timeout)
|
|
298
378
|
uploader.stop()
|
|
299
379
|
|
|
300
380
|
# Close launch
|
|
@@ -303,3 +383,12 @@ def upload(
|
|
|
303
383
|
uploader.stop()
|
|
304
384
|
|
|
305
385
|
_print_summary(uploader)
|
|
386
|
+
|
|
387
|
+
if not fully_drained:
|
|
388
|
+
click.echo(
|
|
389
|
+
"Error: upload did not finish — the consumer stalled and some results "
|
|
390
|
+
"were not sent. Exiting non-zero so this run is not treated as a "
|
|
391
|
+
"successful upload.",
|
|
392
|
+
err=True,
|
|
393
|
+
)
|
|
394
|
+
sys.exit(1)
|