tracera-core 0.1.0rc1__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.
- tracera_core-0.1.0rc1/.gitignore +1 -0
- tracera_core-0.1.0rc1/LICENSE.md +23 -0
- tracera_core-0.1.0rc1/PKG-INFO +50 -0
- tracera_core-0.1.0rc1/README.md +36 -0
- tracera_core-0.1.0rc1/pyproject.toml +35 -0
- tracera_core-0.1.0rc1/src/tracera/__init__.py +35 -0
- tracera_core-0.1.0rc1/src/tracera/bind.py +18 -0
- tracera_core-0.1.0rc1/src/tracera/buffer.py +245 -0
- tracera_core-0.1.0rc1/src/tracera/client.py +557 -0
- tracera_core-0.1.0rc1/src/tracera/config.py +120 -0
- tracera_core-0.1.0rc1/src/tracera/context.py +586 -0
- tracera_core-0.1.0rc1/src/tracera/dotenv.py +97 -0
- tracera_core-0.1.0rc1/src/tracera/env_extras.py +82 -0
- tracera_core-0.1.0rc1/src/tracera/env_snapshot.py +85 -0
- tracera_core-0.1.0rc1/src/tracera/http.py +57 -0
- tracera_core-0.1.0rc1/src/tracera/job_pool.py +56 -0
- tracera_core-0.1.0rc1/src/tracera/log.py +108 -0
- tracera_core-0.1.0rc1/src/tracera/materialize.py +19 -0
- tracera_core-0.1.0rc1/src/tracera/parallel.py +68 -0
- tracera_core-0.1.0rc1/src/tracera/pending.py +125 -0
- tracera_core-0.1.0rc1/src/tracera/registry.py +15 -0
- tracera_core-0.1.0rc1/src/tracera/represent.py +203 -0
- tracera_core-0.1.0rc1/src/tracera/run_handoff.py +227 -0
- tracera_core-0.1.0rc1/src/tracera/runtime.py +69 -0
- tracera_core-0.1.0rc1/src/tracera/session.py +180 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package-local stub so hatchling sdist does not embed the monorepo root .gitignore.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2026 Tracera. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are the
|
|
4
|
+
proprietary and confidential property of Tracera.
|
|
5
|
+
|
|
6
|
+
No license to use, copy, modify, merge, publish, distribute, sublicense,
|
|
7
|
+
and/or sell copies of the Software is granted by default. Any use of the
|
|
8
|
+
Software requires a separate written agreement with Tracera.
|
|
9
|
+
|
|
10
|
+
Unauthorized use, reproduction, or distribution of the Software, in whole
|
|
11
|
+
or in part, is strictly prohibited and may result in civil and criminal
|
|
12
|
+
penalties.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
17
|
+
TRACERA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
18
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
19
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
This is a draft notice pending a full commercial license / EULA.
|
|
22
|
+
Third-party open-source components included with or used by the Software
|
|
23
|
+
remain subject to their respective licenses.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tracera-core
|
|
3
|
+
Version: 0.1.0rc1
|
|
4
|
+
Summary: Tracera Autotest adapter core (HTTP + steps + result upload)
|
|
5
|
+
Project-URL: Homepage, https://tracera.dev/docs/adapters
|
|
6
|
+
Project-URL: Bug-Tracker, https://github.com/tracera-dev/tracera-releases/issues
|
|
7
|
+
Author-email: Tracera <contact@tracera.dev>
|
|
8
|
+
License: SEE LICENSE IN LICENSE.md
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Keywords: adapter,autotest,reporting,test-reporting,testops,tracera
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# tracera-core
|
|
16
|
+
|
|
17
|
+
Shared Autotest HTTP client, step context, and result upload for Tracera adapters.
|
|
18
|
+
|
|
19
|
+
**Requires Python ≥ 3.11.**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install tracera-core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Import package name is **`tracera`**.
|
|
26
|
+
|
|
27
|
+
Foreign thread: capture `get_context()` on the test thread, then wrap the worker in `with run_with_context(ctx):`. Without a pin, steps and comments on that thread are dropped.
|
|
28
|
+
|
|
29
|
+
**Several test processes** (e.g. Pabot, parallel Behave): they share one run automatically — the last process to finish closes it. You do not create the run by hand.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from tracera import step
|
|
33
|
+
|
|
34
|
+
step("title", lambda: None)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@step("open {path}")
|
|
38
|
+
def open_login(path: str): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
with step("title"):
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
async with step("title"):
|
|
45
|
+
...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Used by the official Python adapters. Customer how-to: [Autotest adapters](https://tracera.dev/docs/adapters). Prefer a framework package unless you write a custom runner integration.
|
|
49
|
+
|
|
50
|
+
Plugin / runner helpers (`begin_reporting`, `create_test_context`, …) live in submodules (`tracera.session`, `tracera.context`, …) — not on the `tracera` package root.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# tracera-core
|
|
2
|
+
|
|
3
|
+
Shared Autotest HTTP client, step context, and result upload for Tracera adapters.
|
|
4
|
+
|
|
5
|
+
**Requires Python ≥ 3.11.**
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tracera-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Import package name is **`tracera`**.
|
|
12
|
+
|
|
13
|
+
Foreign thread: capture `get_context()` on the test thread, then wrap the worker in `with run_with_context(ctx):`. Without a pin, steps and comments on that thread are dropped.
|
|
14
|
+
|
|
15
|
+
**Several test processes** (e.g. Pabot, parallel Behave): they share one run automatically — the last process to finish closes it. You do not create the run by hand.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from tracera import step
|
|
19
|
+
|
|
20
|
+
step("title", lambda: None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@step("open {path}")
|
|
24
|
+
def open_login(path: str): ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
with step("title"):
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
async with step("title"):
|
|
31
|
+
...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Used by the official Python adapters. Customer how-to: [Autotest adapters](https://tracera.dev/docs/adapters). Prefer a framework package unless you write a custom runner integration.
|
|
35
|
+
|
|
36
|
+
Plugin / runner helpers (`begin_reporting`, `create_test_context`, …) live in submodules (`tracera.session`, `tracera.context`, …) — not on the `tracera` package root.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tracera-core"
|
|
3
|
+
version = "0.1.0rc1"
|
|
4
|
+
description = "Tracera Autotest adapter core (HTTP + steps + result upload)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "SEE LICENSE IN LICENSE.md" }
|
|
7
|
+
authors = [{ name = "Tracera", email = "contact@tracera.dev" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = ["tracera", "autotest", "test-reporting", "testops", "adapter", "reporting"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.27",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://tracera.dev/docs/adapters"
|
|
16
|
+
Bug-Tracker = "https://github.com/tracera-dev/tracera-releases/issues"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
# Distribution: tracera-core. Import package: tracera (mirrors @tracera/core → import from package).
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["src/tracera"]
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
27
|
+
"src/tracera" = "tracera"
|
|
28
|
+
|
|
29
|
+
# Publish surface: runtime package + docs only (no tests / harness / gitignore).
|
|
30
|
+
[tool.hatch.build.targets.sdist]
|
|
31
|
+
only-include = [
|
|
32
|
+
"src/tracera",
|
|
33
|
+
"LICENSE.md",
|
|
34
|
+
"README.md",
|
|
35
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Tracera Autotest adapter core — customer DX.
|
|
2
|
+
|
|
3
|
+
Public surface: ``step`` / ``comment`` / ``error`` / ``attachment`` /
|
|
4
|
+
``get_context`` / ``run_with_context`` (+ types). How-to:
|
|
5
|
+
https://tracera.dev/docs/adapters
|
|
6
|
+
|
|
7
|
+
Plugin / runner helpers stay in submodules (``tracera.session``, ``tracera.context``,
|
|
8
|
+
``tracera.registry``, ``tracera.runtime``, …) — not on this package root / ``__all__``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from tracera.context import (
|
|
14
|
+
AttachmentMeta,
|
|
15
|
+
TestContext,
|
|
16
|
+
attachment,
|
|
17
|
+
comment,
|
|
18
|
+
error,
|
|
19
|
+
get_context,
|
|
20
|
+
reserve_attachment,
|
|
21
|
+
run_with_context,
|
|
22
|
+
step,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AttachmentMeta",
|
|
27
|
+
"TestContext",
|
|
28
|
+
"attachment",
|
|
29
|
+
"comment",
|
|
30
|
+
"error",
|
|
31
|
+
"get_context",
|
|
32
|
+
"reserve_attachment",
|
|
33
|
+
"run_with_context",
|
|
34
|
+
"step",
|
|
35
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Declarative bind helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class TraceraBind:
|
|
10
|
+
test_case_id: int
|
|
11
|
+
autotest_name: str | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_bind(test_case_id: int, autotest_name: str | None = None) -> TraceraBind:
|
|
15
|
+
return TraceraBind(
|
|
16
|
+
test_case_id=test_case_id,
|
|
17
|
+
autotest_name=autotest_name[:128] if autotest_name else None,
|
|
18
|
+
)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Result buffer, session, soft-disable after consecutive batch failures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Final, Literal
|
|
8
|
+
|
|
9
|
+
from tracera.client import BatchItem, TraceraClient
|
|
10
|
+
from tracera.config import TraceraConfig
|
|
11
|
+
from tracera.context import TestContext, snapshot_payload
|
|
12
|
+
from tracera.env_snapshot import snapshot_env_vars
|
|
13
|
+
from tracera.log import log
|
|
14
|
+
|
|
15
|
+
CONSECUTIVE_BATCH_FAIL_LIMIT: Final[int] = 2
|
|
16
|
+
|
|
17
|
+
Status = Literal["Passed", "Failed", "Skipped"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class PendingResult:
|
|
22
|
+
ctx: TestContext
|
|
23
|
+
status: Status
|
|
24
|
+
duration_minutes: float | None
|
|
25
|
+
payload: dict[str, Any] | None = None
|
|
26
|
+
env_vars_extra: dict[str, str] | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ReportSession:
|
|
31
|
+
client: TraceraClient
|
|
32
|
+
config: TraceraConfig
|
|
33
|
+
test_run_id: int
|
|
34
|
+
owned_by_adapter: bool
|
|
35
|
+
reporting_enabled: bool = True
|
|
36
|
+
failed_report: int = 0
|
|
37
|
+
unsent_results: int = 0
|
|
38
|
+
last_error: str | None = None
|
|
39
|
+
"""When True, finish via peer handoff refcount (Pabot / Behave / …)."""
|
|
40
|
+
peer_handoff: bool = False
|
|
41
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, repr=False, compare=False)
|
|
42
|
+
|
|
43
|
+
def note_failed_report(self, n: int = 1) -> None:
|
|
44
|
+
with self._lock:
|
|
45
|
+
self.failed_report += n
|
|
46
|
+
|
|
47
|
+
def note_unsent_results(self, n: int = 1) -> None:
|
|
48
|
+
with self._lock:
|
|
49
|
+
self.unsent_results += n
|
|
50
|
+
|
|
51
|
+
def clear_one_failed_report(self) -> None:
|
|
52
|
+
with self._lock:
|
|
53
|
+
self.failed_report = max(0, self.failed_report - 1)
|
|
54
|
+
|
|
55
|
+
def disable_reporting(self) -> None:
|
|
56
|
+
with self._lock:
|
|
57
|
+
self.reporting_enabled = False
|
|
58
|
+
|
|
59
|
+
def set_last_error(self, message: str | None) -> None:
|
|
60
|
+
with self._lock:
|
|
61
|
+
self.last_error = message
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _create_session(
|
|
65
|
+
client: TraceraClient,
|
|
66
|
+
config: TraceraConfig,
|
|
67
|
+
test_run_id: int,
|
|
68
|
+
owned_by_adapter: bool,
|
|
69
|
+
*,
|
|
70
|
+
peer_handoff: bool = False,
|
|
71
|
+
) -> ReportSession:
|
|
72
|
+
return ReportSession(
|
|
73
|
+
client=client,
|
|
74
|
+
config=config,
|
|
75
|
+
test_run_id=test_run_id,
|
|
76
|
+
owned_by_adapter=owned_by_adapter,
|
|
77
|
+
peer_handoff=peer_handoff,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ResultBuffer:
|
|
82
|
+
def __init__(self, session: ReportSession) -> None:
|
|
83
|
+
self._session = session
|
|
84
|
+
self._pending: list[PendingResult] = []
|
|
85
|
+
self._lock = threading.Lock()
|
|
86
|
+
self._timer: threading.Timer | None = None
|
|
87
|
+
self._flushing = threading.Lock()
|
|
88
|
+
self._transport_failures: dict[int, int] = {}
|
|
89
|
+
self._consecutive_batch_failures = 0
|
|
90
|
+
|
|
91
|
+
def enqueue(self, item: PendingResult) -> None:
|
|
92
|
+
if not self._session.reporting_enabled:
|
|
93
|
+
return
|
|
94
|
+
if item.ctx.test_case_id is None or not isinstance(item.ctx.test_case_id, int):
|
|
95
|
+
return
|
|
96
|
+
with self._lock:
|
|
97
|
+
self._pending.append(item)
|
|
98
|
+
if len(self._pending) >= self._session.config.flush_max_items:
|
|
99
|
+
self._schedule_flush(immediate=True)
|
|
100
|
+
return
|
|
101
|
+
if self._timer is None:
|
|
102
|
+
self._schedule_flush(immediate=False)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def pending_count(self) -> int:
|
|
106
|
+
with self._lock:
|
|
107
|
+
return len(self._pending)
|
|
108
|
+
|
|
109
|
+
def _schedule_flush(self, *, immediate: bool) -> None:
|
|
110
|
+
if self._timer is not None:
|
|
111
|
+
self._timer.cancel()
|
|
112
|
+
self._timer = None
|
|
113
|
+
if immediate:
|
|
114
|
+
threading.Thread(target=self.flush, daemon=True).start()
|
|
115
|
+
return
|
|
116
|
+
delay = self._session.config.flush_max_ms / 1000
|
|
117
|
+
self._timer = threading.Timer(delay, self.flush)
|
|
118
|
+
self._timer.daemon = True
|
|
119
|
+
self._timer.start()
|
|
120
|
+
|
|
121
|
+
def flush(self) -> None:
|
|
122
|
+
with self._flushing:
|
|
123
|
+
self._flush_once()
|
|
124
|
+
if self._session.reporting_enabled and self.pending_count > 0:
|
|
125
|
+
self._flush_once()
|
|
126
|
+
|
|
127
|
+
def _flush_once(self) -> None:
|
|
128
|
+
with self._lock:
|
|
129
|
+
if self._timer is not None:
|
|
130
|
+
self._timer.cancel()
|
|
131
|
+
self._timer = None
|
|
132
|
+
if not self._session.reporting_enabled or not self._pending:
|
|
133
|
+
return
|
|
134
|
+
pending_snapshot = list(self._pending)
|
|
135
|
+
self._pending.clear()
|
|
136
|
+
|
|
137
|
+
retry_later: list[PendingResult] = []
|
|
138
|
+
idx = 0
|
|
139
|
+
while self._session.reporting_enabled and idx < len(pending_snapshot):
|
|
140
|
+
batch = pending_snapshot[idx : idx + self._session.config.flush_max_items]
|
|
141
|
+
idx += len(batch)
|
|
142
|
+
log.debug(f"Tracera: sent {len(batch)} result(s)")
|
|
143
|
+
base_env = snapshot_env_vars()
|
|
144
|
+
items: list[BatchItem] = []
|
|
145
|
+
for p in batch:
|
|
146
|
+
if isinstance(p.payload, dict):
|
|
147
|
+
snap = p.payload
|
|
148
|
+
else:
|
|
149
|
+
snap = snapshot_payload(p.ctx)
|
|
150
|
+
error_message = snap.get("errorMessage")
|
|
151
|
+
if error_message is None:
|
|
152
|
+
error_message = p.ctx.result_error
|
|
153
|
+
comment = snap.get("comment")
|
|
154
|
+
if comment is None:
|
|
155
|
+
comment = p.ctx.result_comment
|
|
156
|
+
env_vars = {**base_env, **p.env_vars_extra} if p.env_vars_extra else base_env
|
|
157
|
+
items.append(
|
|
158
|
+
BatchItem(
|
|
159
|
+
is_automated=True,
|
|
160
|
+
test_case_id=p.ctx.test_case_id or 0,
|
|
161
|
+
test_run_id=self._session.test_run_id,
|
|
162
|
+
result=p.status,
|
|
163
|
+
autotest_name=p.ctx.autotest_name or None,
|
|
164
|
+
duration_minutes=p.duration_minutes,
|
|
165
|
+
setup=snap.get("setup") or [],
|
|
166
|
+
test=snap.get("test") or [],
|
|
167
|
+
teardown=snap.get("teardown") or [],
|
|
168
|
+
attachments=snap.get("attachments") or [],
|
|
169
|
+
error_message=error_message,
|
|
170
|
+
comment=comment,
|
|
171
|
+
env_vars=env_vars,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
try:
|
|
175
|
+
res = self._session.client.post_results_batch(self._session.test_run_id, items)
|
|
176
|
+
self._consecutive_batch_failures = 0
|
|
177
|
+
for p in batch:
|
|
178
|
+
self._note_transport_success(p)
|
|
179
|
+
for r in res.results:
|
|
180
|
+
if r.get("ok"):
|
|
181
|
+
continue
|
|
182
|
+
self._session.note_failed_report()
|
|
183
|
+
self._session.set_last_error(r.get("error") or "item_failed")
|
|
184
|
+
ridx = int(r.get("index", -1))
|
|
185
|
+
test_case_id = items[ridx].test_case_id if 0 <= ridx < len(items) else None
|
|
186
|
+
case_part = (
|
|
187
|
+
f"testCaseId={test_case_id}"
|
|
188
|
+
if test_case_id is not None
|
|
189
|
+
else f"batch index {ridx}"
|
|
190
|
+
)
|
|
191
|
+
log.error(
|
|
192
|
+
f"Tracera: server rejected result ({case_part}): {r.get('error') or 'unknown'}"
|
|
193
|
+
)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self._session.set_last_error(str(e))
|
|
196
|
+
log.error(f"Tracera: {self._session.last_error} — requeued")
|
|
197
|
+
for p in batch:
|
|
198
|
+
self._note_transport_failure(p)
|
|
199
|
+
retry_later.extend(batch)
|
|
200
|
+
self._consecutive_batch_failures += 1
|
|
201
|
+
if self._consecutive_batch_failures >= CONSECUTIVE_BATCH_FAIL_LIMIT:
|
|
202
|
+
self._session.disable_reporting()
|
|
203
|
+
log.error(
|
|
204
|
+
f"Tracera: stopped reporting after {CONSECUTIVE_BATCH_FAIL_LIMIT} "
|
|
205
|
+
"failed batch uploads"
|
|
206
|
+
)
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
# Soft-disable mid-flush: never-attempted snapshot batches must stay
|
|
210
|
+
# buffered (count as not uploaded) instead of vanishing.
|
|
211
|
+
if idx < len(pending_snapshot):
|
|
212
|
+
retry_later.extend(pending_snapshot[idx:])
|
|
213
|
+
if retry_later:
|
|
214
|
+
with self._lock:
|
|
215
|
+
self._pending = retry_later + self._pending
|
|
216
|
+
|
|
217
|
+
def _note_transport_success(self, item: PendingResult) -> None:
|
|
218
|
+
key = id(item)
|
|
219
|
+
if self._transport_failures.get(key, 0) == 0:
|
|
220
|
+
return
|
|
221
|
+
self._session.clear_one_failed_report()
|
|
222
|
+
self._transport_failures.pop(key, None)
|
|
223
|
+
|
|
224
|
+
def _note_transport_failure(self, item: PendingResult) -> None:
|
|
225
|
+
key = id(item)
|
|
226
|
+
n = self._transport_failures.get(key, 0) + 1
|
|
227
|
+
self._transport_failures[key] = n
|
|
228
|
+
if n == 1:
|
|
229
|
+
self._session.note_failed_report()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _print_summary(session: ReportSession) -> None:
|
|
233
|
+
log.link(session.client.run_url(session.test_run_id))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def finish_owned_run(session: ReportSession) -> None:
|
|
237
|
+
if not session.owned_by_adapter:
|
|
238
|
+
return
|
|
239
|
+
try:
|
|
240
|
+
session.client.finish_run(session.test_run_id)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
session.set_last_error(str(e))
|
|
243
|
+
session.note_failed_report()
|
|
244
|
+
msg = session.last_error or str(e)
|
|
245
|
+
log.error(msg if msg.startswith("Tracera:") else f"Tracera: {msg}")
|