docus 0.1.1__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.
- docus-0.1.1/PKG-INFO +78 -0
- docus-0.1.1/README.md +62 -0
- docus-0.1.1/docus.egg-info/PKG-INFO +78 -0
- docus-0.1.1/docus.egg-info/SOURCES.txt +9 -0
- docus-0.1.1/docus.egg-info/dependency_links.txt +1 -0
- docus-0.1.1/docus.egg-info/entry_points.txt +6 -0
- docus-0.1.1/docus.egg-info/requires.txt +1 -0
- docus-0.1.1/docus.egg-info/top_level.txt +1 -0
- docus-0.1.1/pyproject.toml +33 -0
- docus-0.1.1/pytest_recorder_client.py +209 -0
- docus-0.1.1/setup.cfg +4 -0
docus-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docus
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Pytest client for the Docus test recorder service
|
|
5
|
+
Project-URL: Homepage, https://github.com/dooblai/pydocusclient
|
|
6
|
+
Project-URL: Source, https://github.com/dooblai/pydocusclient
|
|
7
|
+
Project-URL: Issues, https://github.com/dooblai/pydocusclient/issues
|
|
8
|
+
Classifier: Framework :: Pytest
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: pytest>=7
|
|
16
|
+
|
|
17
|
+
# docus
|
|
18
|
+
|
|
19
|
+
An installable pytest plugin that sends test sessions and results to a Docus
|
|
20
|
+
test recorder service. It uses the pytest11 entry point, so existing test
|
|
21
|
+
files do not need to change.
|
|
22
|
+
|
|
23
|
+
## Install and run
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install docus
|
|
27
|
+
TEST_RECORDER_URL=https://recorder.example.com pytest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The plugin stays inactive until `TEST_RECORDER_URL` or `--recorder-url` is
|
|
31
|
+
provided. Set `TEST_RECORDER_API_TOKEN` when the service requires a bearer
|
|
32
|
+
token. Set `TEST_RECORDER_REPO` to the canonical Git repository identifier;
|
|
33
|
+
otherwise the plugin uses `GITHUB_REPOSITORY` or Git origin. The optional
|
|
34
|
+
`TEST_RECORDER_ACTOR` overrides the current user.
|
|
35
|
+
|
|
36
|
+
The plugin records collected tests, test results, pytest phase durations,
|
|
37
|
+
captured output, Git commit and branch, and a session ID. It sends a batch
|
|
38
|
+
at session end. If delivery fails, pytest results are unchanged and the batch
|
|
39
|
+
is saved under `.test-recorder/spool`. Retry with:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
TEST_RECORDER_URL=https://recorder.example.com pydocus-resend
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To keep a test identity across a pytest function rename, mark it with
|
|
46
|
+
`@pytest.mark.record_id("logical-name")`. Parameter IDs should remain stable.
|
|
47
|
+
Output and failure fields are capped at 64 KiB each; the batch indicates when
|
|
48
|
+
truncation occurred.
|
|
49
|
+
|
|
50
|
+
The Docus service is developed at
|
|
51
|
+
[dooblai/docus](https://github.com/dooblai/docus). This package only depends
|
|
52
|
+
on pytest and the Python standard library. Other languages can send the same
|
|
53
|
+
JSON event format directly to the service.
|
|
54
|
+
|
|
55
|
+
## Build and release to PyPI
|
|
56
|
+
|
|
57
|
+
1. Install the package and run `python testing_suite_client.py`.
|
|
58
|
+
2. Run `python -m pip install build twine`, `python -m build`, and
|
|
59
|
+
`python -m twine check dist/*` from this repository.
|
|
60
|
+
3. Create a GitHub environment named `pypi` in `dooblai/pydocusclient`.
|
|
61
|
+
4. In PyPI, configure a **pending trusted publisher** for project name
|
|
62
|
+
`docus`: owner `dooblai`, repository `pydocusclient`,
|
|
63
|
+
workflow filename `release.yml`, environment `pypi`. This must match
|
|
64
|
+
`.github/workflows/release.yml` exactly.
|
|
65
|
+
5. Push a version tag such as `v0.1.1`, then publish a GitHub Release for
|
|
66
|
+
that tag. The release workflow builds a wheel and source distribution and
|
|
67
|
+
publishes them using GitHub OIDC. No long lived PyPI API token is needed.
|
|
68
|
+
6. Verify with `pip install docus` in a fresh environment.
|
|
69
|
+
|
|
70
|
+
PyPI does not reserve a name when a pending publisher is configured. Check
|
|
71
|
+
that `docus` is available immediately before the first release.
|
|
72
|
+
For later releases, update the version in `pyproject.toml` and use a new
|
|
73
|
+
matching GitHub tag. Choose and add a license before public distribution if
|
|
74
|
+
you intend to grant reuse rights.
|
|
75
|
+
|
|
76
|
+
See the [PyPI trusted publishing guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
77
|
+
and [Python packaging guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
|
78
|
+
for the current publishing steps.
|
docus-0.1.1/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# docus
|
|
2
|
+
|
|
3
|
+
An installable pytest plugin that sends test sessions and results to a Docus
|
|
4
|
+
test recorder service. It uses the pytest11 entry point, so existing test
|
|
5
|
+
files do not need to change.
|
|
6
|
+
|
|
7
|
+
## Install and run
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install docus
|
|
11
|
+
TEST_RECORDER_URL=https://recorder.example.com pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The plugin stays inactive until `TEST_RECORDER_URL` or `--recorder-url` is
|
|
15
|
+
provided. Set `TEST_RECORDER_API_TOKEN` when the service requires a bearer
|
|
16
|
+
token. Set `TEST_RECORDER_REPO` to the canonical Git repository identifier;
|
|
17
|
+
otherwise the plugin uses `GITHUB_REPOSITORY` or Git origin. The optional
|
|
18
|
+
`TEST_RECORDER_ACTOR` overrides the current user.
|
|
19
|
+
|
|
20
|
+
The plugin records collected tests, test results, pytest phase durations,
|
|
21
|
+
captured output, Git commit and branch, and a session ID. It sends a batch
|
|
22
|
+
at session end. If delivery fails, pytest results are unchanged and the batch
|
|
23
|
+
is saved under `.test-recorder/spool`. Retry with:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
TEST_RECORDER_URL=https://recorder.example.com pydocus-resend
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To keep a test identity across a pytest function rename, mark it with
|
|
30
|
+
`@pytest.mark.record_id("logical-name")`. Parameter IDs should remain stable.
|
|
31
|
+
Output and failure fields are capped at 64 KiB each; the batch indicates when
|
|
32
|
+
truncation occurred.
|
|
33
|
+
|
|
34
|
+
The Docus service is developed at
|
|
35
|
+
[dooblai/docus](https://github.com/dooblai/docus). This package only depends
|
|
36
|
+
on pytest and the Python standard library. Other languages can send the same
|
|
37
|
+
JSON event format directly to the service.
|
|
38
|
+
|
|
39
|
+
## Build and release to PyPI
|
|
40
|
+
|
|
41
|
+
1. Install the package and run `python testing_suite_client.py`.
|
|
42
|
+
2. Run `python -m pip install build twine`, `python -m build`, and
|
|
43
|
+
`python -m twine check dist/*` from this repository.
|
|
44
|
+
3. Create a GitHub environment named `pypi` in `dooblai/pydocusclient`.
|
|
45
|
+
4. In PyPI, configure a **pending trusted publisher** for project name
|
|
46
|
+
`docus`: owner `dooblai`, repository `pydocusclient`,
|
|
47
|
+
workflow filename `release.yml`, environment `pypi`. This must match
|
|
48
|
+
`.github/workflows/release.yml` exactly.
|
|
49
|
+
5. Push a version tag such as `v0.1.1`, then publish a GitHub Release for
|
|
50
|
+
that tag. The release workflow builds a wheel and source distribution and
|
|
51
|
+
publishes them using GitHub OIDC. No long lived PyPI API token is needed.
|
|
52
|
+
6. Verify with `pip install docus` in a fresh environment.
|
|
53
|
+
|
|
54
|
+
PyPI does not reserve a name when a pending publisher is configured. Check
|
|
55
|
+
that `docus` is available immediately before the first release.
|
|
56
|
+
For later releases, update the version in `pyproject.toml` and use a new
|
|
57
|
+
matching GitHub tag. Choose and add a license before public distribution if
|
|
58
|
+
you intend to grant reuse rights.
|
|
59
|
+
|
|
60
|
+
See the [PyPI trusted publishing guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
61
|
+
and [Python packaging guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
|
62
|
+
for the current publishing steps.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docus
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Pytest client for the Docus test recorder service
|
|
5
|
+
Project-URL: Homepage, https://github.com/dooblai/pydocusclient
|
|
6
|
+
Project-URL: Source, https://github.com/dooblai/pydocusclient
|
|
7
|
+
Project-URL: Issues, https://github.com/dooblai/pydocusclient/issues
|
|
8
|
+
Classifier: Framework :: Pytest
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: pytest>=7
|
|
16
|
+
|
|
17
|
+
# docus
|
|
18
|
+
|
|
19
|
+
An installable pytest plugin that sends test sessions and results to a Docus
|
|
20
|
+
test recorder service. It uses the pytest11 entry point, so existing test
|
|
21
|
+
files do not need to change.
|
|
22
|
+
|
|
23
|
+
## Install and run
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install docus
|
|
27
|
+
TEST_RECORDER_URL=https://recorder.example.com pytest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The plugin stays inactive until `TEST_RECORDER_URL` or `--recorder-url` is
|
|
31
|
+
provided. Set `TEST_RECORDER_API_TOKEN` when the service requires a bearer
|
|
32
|
+
token. Set `TEST_RECORDER_REPO` to the canonical Git repository identifier;
|
|
33
|
+
otherwise the plugin uses `GITHUB_REPOSITORY` or Git origin. The optional
|
|
34
|
+
`TEST_RECORDER_ACTOR` overrides the current user.
|
|
35
|
+
|
|
36
|
+
The plugin records collected tests, test results, pytest phase durations,
|
|
37
|
+
captured output, Git commit and branch, and a session ID. It sends a batch
|
|
38
|
+
at session end. If delivery fails, pytest results are unchanged and the batch
|
|
39
|
+
is saved under `.test-recorder/spool`. Retry with:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
TEST_RECORDER_URL=https://recorder.example.com pydocus-resend
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To keep a test identity across a pytest function rename, mark it with
|
|
46
|
+
`@pytest.mark.record_id("logical-name")`. Parameter IDs should remain stable.
|
|
47
|
+
Output and failure fields are capped at 64 KiB each; the batch indicates when
|
|
48
|
+
truncation occurred.
|
|
49
|
+
|
|
50
|
+
The Docus service is developed at
|
|
51
|
+
[dooblai/docus](https://github.com/dooblai/docus). This package only depends
|
|
52
|
+
on pytest and the Python standard library. Other languages can send the same
|
|
53
|
+
JSON event format directly to the service.
|
|
54
|
+
|
|
55
|
+
## Build and release to PyPI
|
|
56
|
+
|
|
57
|
+
1. Install the package and run `python testing_suite_client.py`.
|
|
58
|
+
2. Run `python -m pip install build twine`, `python -m build`, and
|
|
59
|
+
`python -m twine check dist/*` from this repository.
|
|
60
|
+
3. Create a GitHub environment named `pypi` in `dooblai/pydocusclient`.
|
|
61
|
+
4. In PyPI, configure a **pending trusted publisher** for project name
|
|
62
|
+
`docus`: owner `dooblai`, repository `pydocusclient`,
|
|
63
|
+
workflow filename `release.yml`, environment `pypi`. This must match
|
|
64
|
+
`.github/workflows/release.yml` exactly.
|
|
65
|
+
5. Push a version tag such as `v0.1.1`, then publish a GitHub Release for
|
|
66
|
+
that tag. The release workflow builds a wheel and source distribution and
|
|
67
|
+
publishes them using GitHub OIDC. No long lived PyPI API token is needed.
|
|
68
|
+
6. Verify with `pip install docus` in a fresh environment.
|
|
69
|
+
|
|
70
|
+
PyPI does not reserve a name when a pending publisher is configured. Check
|
|
71
|
+
that `docus` is available immediately before the first release.
|
|
72
|
+
For later releases, update the version in `pyproject.toml` and use a new
|
|
73
|
+
matching GitHub tag. Choose and add a license before public distribution if
|
|
74
|
+
you intend to grant reuse rights.
|
|
75
|
+
|
|
76
|
+
See the [PyPI trusted publishing guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
77
|
+
and [Python packaging guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
|
78
|
+
for the current publishing steps.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest>=7
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest_recorder_client
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "docus"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Pytest client for the Docus test recorder service"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = ["pytest>=7"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Framework :: Pytest",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Software Development :: Testing",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/dooblai/pydocusclient"
|
|
22
|
+
Source = "https://github.com/dooblai/pydocusclient"
|
|
23
|
+
Issues = "https://github.com/dooblai/pydocusclient/issues"
|
|
24
|
+
|
|
25
|
+
[project.entry-points.pytest11]
|
|
26
|
+
pydocusclient = "pytest_recorder_client"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
pydocus-resend = "pytest_recorder_client:resend_main"
|
|
30
|
+
test-recorder-resend = "pytest_recorder_client:resend_main"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
py-modules = ["pytest_recorder_client"]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Installable pytest adapter for the Test Recorder HTTP API."""
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
import argparse
|
|
4
|
+
import getpass
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import socket
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.request
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def timestamp():
|
|
18
|
+
return datetime.now(timezone.utc).isoformat(timespec="microseconds")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def repository_id(root, override=None):
|
|
22
|
+
value = override or os.getenv("TEST_RECORDER_REPO")
|
|
23
|
+
if value:
|
|
24
|
+
return value
|
|
25
|
+
if os.getenv("GITHUB_REPOSITORY"):
|
|
26
|
+
return "github.com/" + os.environ["GITHUB_REPOSITORY"]
|
|
27
|
+
try:
|
|
28
|
+
value = subprocess.check_output(["git", "-C", str(root), "remote", "get-url", "origin"],
|
|
29
|
+
text=True, stderr=subprocess.DEVNULL).strip()
|
|
30
|
+
except (OSError, subprocess.CalledProcessError):
|
|
31
|
+
raise ValueError("Set TEST_RECORDER_REPO when Git origin is unavailable")
|
|
32
|
+
value = value.replace("git@", "", 1).replace(":", "/", 1) if value.startswith("git@") else value
|
|
33
|
+
value = re.sub("^https?://", "", value)
|
|
34
|
+
return value.removesuffix(".git").rstrip("/")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def git_info(root):
|
|
38
|
+
info = {}
|
|
39
|
+
for key, args in (("commit", ["rev-parse", "HEAD"]), ("branch", ["branch", "--show-current"])):
|
|
40
|
+
try:
|
|
41
|
+
info[key] = subprocess.check_output(["git", "-C", str(root), *args], text=True,
|
|
42
|
+
stderr=subprocess.DEVNULL).strip() or None
|
|
43
|
+
except (OSError, subprocess.CalledProcessError):
|
|
44
|
+
info[key] = None
|
|
45
|
+
try:
|
|
46
|
+
info["dirty"] = bool(subprocess.check_output(["git", "-C", str(root), "status", "--porcelain"],
|
|
47
|
+
text=True, stderr=subprocess.DEVNULL).strip())
|
|
48
|
+
except (OSError, subprocess.CalledProcessError):
|
|
49
|
+
info["dirty"] = None
|
|
50
|
+
return info
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def post_batch(url, token, payload):
|
|
54
|
+
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
55
|
+
headers = {"Content-Type": "application/json"}
|
|
56
|
+
if token:
|
|
57
|
+
headers["Authorization"] = "Bearer " + token
|
|
58
|
+
request = urllib.request.Request(url.rstrip("/") + "/v1/batches", data=body,
|
|
59
|
+
headers=headers, method="POST")
|
|
60
|
+
with urllib.request.urlopen(request, timeout=10) as response:
|
|
61
|
+
return json.load(response)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def pytest_addoption(parser):
|
|
65
|
+
group = parser.getgroup("test-recorder")
|
|
66
|
+
group.addoption("--recorder-url", default=None, help="Test Recorder service URL")
|
|
67
|
+
group.addoption("--recorder-repo", default=None, help="Canonical repository ID")
|
|
68
|
+
group.addoption("--recorder-spool", default=None, help="Directory for unsent result batches")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def pytest_configure(config):
|
|
72
|
+
config.addinivalue_line("markers", "record_id(value): stable case name for recorder identity")
|
|
73
|
+
url = config.getoption("--recorder-url") or os.getenv("TEST_RECORDER_URL")
|
|
74
|
+
if url:
|
|
75
|
+
config.pluginmanager.register(RecorderPlugin(config, url), "test-recorder-client-active")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RecorderPlugin:
|
|
79
|
+
def __init__(self, config, url):
|
|
80
|
+
self.root = Path(str(config.rootpath)).resolve()
|
|
81
|
+
self.repository = repository_id(self.root, config.getoption("--recorder-repo"))
|
|
82
|
+
self.url = url
|
|
83
|
+
self.token = os.getenv("TEST_RECORDER_API_TOKEN")
|
|
84
|
+
self.spool = Path(config.getoption("--recorder-spool") or
|
|
85
|
+
os.getenv("TEST_RECORDER_SPOOL_DIR") or
|
|
86
|
+
self.root / ".test-recorder" / "spool")
|
|
87
|
+
self.sid = str(uuid4())
|
|
88
|
+
self.items = {}
|
|
89
|
+
self.reports = {}
|
|
90
|
+
self.error = None
|
|
91
|
+
git = git_info(self.root)
|
|
92
|
+
self.events = [{
|
|
93
|
+
"type": "session_start", "id": self.sid, "repository": self.repository,
|
|
94
|
+
"actor": os.getenv("TEST_RECORDER_ACTOR") or getpass.getuser(),
|
|
95
|
+
"commit": git["commit"], "started_at": timestamp(), "git": git,
|
|
96
|
+
"host": socket.gethostname(),
|
|
97
|
+
"framework": {"name": "pytest", "version": __import__("pytest").__version__},
|
|
98
|
+
"ci": {"run_id": os.getenv("GITHUB_RUN_ID")} if os.getenv("GITHUB_RUN_ID") else None,
|
|
99
|
+
}]
|
|
100
|
+
|
|
101
|
+
def pytest_collection_finish(self, session):
|
|
102
|
+
for item in session.items:
|
|
103
|
+
try:
|
|
104
|
+
path = Path(str(item.path)).resolve().relative_to(self.root).as_posix()
|
|
105
|
+
except ValueError:
|
|
106
|
+
warnings.warn("Recorder skipped test outside repository: " + item.nodeid)
|
|
107
|
+
continue
|
|
108
|
+
parts = item.nodeid.split("::")
|
|
109
|
+
display = parts[-1]
|
|
110
|
+
name, separator, variant = display.partition("[")
|
|
111
|
+
if not separator:
|
|
112
|
+
name, variant = display, ""
|
|
113
|
+
elif variant.endswith("]"):
|
|
114
|
+
variant = variant[:-1]
|
|
115
|
+
marker = item.get_closest_marker("record_id")
|
|
116
|
+
if marker and marker.args:
|
|
117
|
+
name = str(marker.args[0])
|
|
118
|
+
test = {"repository": self.repository, "path": path,
|
|
119
|
+
"namespace": "::".join(parts[1:-1]), "name": name, "variant": variant}
|
|
120
|
+
self.items[item.nodeid] = test
|
|
121
|
+
self.events.append({"type": "observe", "test": test,
|
|
122
|
+
"commit": self.events[0]["commit"], "seen_at": timestamp()})
|
|
123
|
+
|
|
124
|
+
def pytest_runtest_logreport(self, report):
|
|
125
|
+
self.reports.setdefault(report.nodeid, []).append(report)
|
|
126
|
+
if report.when == "teardown":
|
|
127
|
+
self._record(report.nodeid, self.reports.pop(report.nodeid))
|
|
128
|
+
|
|
129
|
+
def _record(self, nodeid, reports):
|
|
130
|
+
test = self.items.get(nodeid)
|
|
131
|
+
if test is None or not reports:
|
|
132
|
+
return
|
|
133
|
+
failures = [report for report in reports if report.failed]
|
|
134
|
+
skips = [report for report in reports if report.skipped]
|
|
135
|
+
if failures:
|
|
136
|
+
status = "xpassed" if any(getattr(report, "wasxfail", None) for report in failures) else (
|
|
137
|
+
"failed" if any(report.when == "call" for report in failures) else "error")
|
|
138
|
+
elif any(getattr(report, "wasxfail", None) for report in reports if report.passed):
|
|
139
|
+
status = "xpassed"
|
|
140
|
+
elif skips:
|
|
141
|
+
status = "xfailed" if any(getattr(report, "wasxfail", None) for report in skips) else "skipped"
|
|
142
|
+
else:
|
|
143
|
+
status = "passed"
|
|
144
|
+
at = lambda seconds: datetime.fromtimestamp(seconds, timezone.utc).isoformat(timespec="microseconds")
|
|
145
|
+
output = {}
|
|
146
|
+
for key, field in (("stdout", "capstdout"), ("stderr", "capstderr")):
|
|
147
|
+
value = chr(10).join(getattr(report, field, "") for report in reports
|
|
148
|
+
if getattr(report, field, ""))
|
|
149
|
+
output[key] = value[-65536:] if value else None
|
|
150
|
+
output[key + "_truncated"] = len(value) > 65536
|
|
151
|
+
trace = chr(10).join(report.longreprtext for report in failures
|
|
152
|
+
if getattr(report, "longreprtext", ""))
|
|
153
|
+
self.events.append({
|
|
154
|
+
"type": "result", "id": str(uuid4()), "test": test, "sid": self.sid,
|
|
155
|
+
"status": status, "started_at": at(min(report.start for report in reports)),
|
|
156
|
+
"finished_at": at(max(report.stop for report in reports)),
|
|
157
|
+
"nodeid": nodeid, "phases": [
|
|
158
|
+
{"phase": report.when, "outcome": report.outcome, "duration_s": report.duration}
|
|
159
|
+
for report in reports],
|
|
160
|
+
"duration_s": sum(report.duration for report in reports),
|
|
161
|
+
"failure": trace[-65536:] if trace else None,
|
|
162
|
+
"failure_truncated": len(trace) > 65536,
|
|
163
|
+
**output,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
def pytest_sessionfinish(self, session, exitstatus):
|
|
167
|
+
for nodeid, reports in list(self.reports.items()):
|
|
168
|
+
self._record(nodeid, reports)
|
|
169
|
+
self.reports.clear()
|
|
170
|
+
self.events.append({"type": "session_finish", "sid": self.sid,
|
|
171
|
+
"finished_at": timestamp(), "exit_code": int(exitstatus),
|
|
172
|
+
"collected": len(self.items)})
|
|
173
|
+
payload = {"events": self.events}
|
|
174
|
+
try:
|
|
175
|
+
post_batch(self.url, self.token, payload)
|
|
176
|
+
except Exception as error:
|
|
177
|
+
self.error = str(error)
|
|
178
|
+
try:
|
|
179
|
+
self.spool.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
target = self.spool / (self.sid + ".json")
|
|
181
|
+
target.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
|
182
|
+
warnings.warn("Test Recorder delivery failed; saved " + str(target) +
|
|
183
|
+
": " + self.error)
|
|
184
|
+
except OSError as spool_error:
|
|
185
|
+
warnings.warn("Test Recorder delivery and spooling failed: " +
|
|
186
|
+
str(error) + "; " + str(spool_error))
|
|
187
|
+
|
|
188
|
+
def pytest_terminal_summary(self, terminalreporter):
|
|
189
|
+
terminalreporter.write_line("Test Recorder session: " + self.sid)
|
|
190
|
+
if self.error:
|
|
191
|
+
terminalreporter.write_line("Test Recorder delivery failed: " + self.error)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def resend_main(argv=None):
|
|
195
|
+
parser = argparse.ArgumentParser(description="Resend saved Test Recorder batches")
|
|
196
|
+
parser.add_argument("--url", default=os.getenv("TEST_RECORDER_URL"), required=False)
|
|
197
|
+
parser.add_argument("--spool", default=os.getenv("TEST_RECORDER_SPOOL_DIR", ".test-recorder/spool"))
|
|
198
|
+
args = parser.parse_args(argv)
|
|
199
|
+
if not args.url:
|
|
200
|
+
parser.error("Set --url or TEST_RECORDER_URL")
|
|
201
|
+
for path in sorted(Path(args.spool).glob("*.json")):
|
|
202
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
203
|
+
post_batch(args.url, os.getenv("TEST_RECORDER_API_TOKEN"), payload)
|
|
204
|
+
path.unlink()
|
|
205
|
+
print("Sent " + str(path))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
resend_main()
|
docus-0.1.1/setup.cfg
ADDED