Skip to content

Commit 8ddc56d

Browse files
committed
Manage test results logging and metadata via pytest options
1 parent 14eacbe commit 8ddc56d

5 files changed

Lines changed: 100 additions & 31 deletions

File tree

python/lib/sift_client/_internal/low_level_wrappers/test_results.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import json
55
import logging
66
import re
7-
import sys
87
import uuid
98
from dataclasses import dataclass, field
109
from pathlib import Path
@@ -376,16 +375,7 @@ async def create_test_report(
376375
)
377376
return TestReport._from_proto(simulated_proto)
378377

379-
try:
380-
response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestReport(
381-
request
382-
)
383-
except Exception:
384-
print(
385-
f"AAAA {self._grpc_client._config.uri} {self._grpc_client._config.use_ssl}",
386-
file=sys.stderr,
387-
)
388-
raise
378+
response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestReport(request)
389379
grpc_test_report = cast("CreateTestReportResponse", response).test_report
390380
return TestReport._from_proto(grpc_test_report)
391381

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
"""Override report_context to disable log file simulation for integration tests in this directory so that we can exercise the context manager when no log file is provided."""
1+
import pytest
22

3-
from sift_client.util.test_results.pytest_util import (
4-
report_context_no_logging as report_context, # noqa: F401
5-
)
3+
4+
def pytest_addoption(parser: pytest.Parser) -> None:
5+
existing_options = [opt.names() for opt in parser._anonymous.options]
6+
# Flatten the list of lists into a single list of strings
7+
flat_options = [item for sublist in existing_options for item in sublist]
8+
if not any("--sift-test-results-log-file" in name for name in flat_options):
9+
parser.addoption("--sift-test-results-log-file", action="store_true", default="false")
10+
11+
12+
def pytest_configure(config: pytest.Config) -> None:
13+
"""Configure the pytest configuration to disable the Sift test results log file."""
14+
config.option.sift_test_results_log_file = False

python/lib/sift_client/util/test_results/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ def main(self):
5858
- If you want each module(file) to be marked as a step w/ each test as a substep, import the `module_substep` fixture as well.
5959
- The `report_context` fixture requires a fixture `sift_client` returning an `SiftClient` instance to be passed in.
6060
61-
Note: FedRAMP users: report_context will log test results to a temp file to avoid API calls during test execution. If this is a shared environment, you should import the `report_context_no_logging` fixture instead.
61+
Note: FedRAMP users: report_context will log test results to a temp file to avoid API calls during test execution. If this is a shared environment, you can disable logging by passing ``--sift-test-results-log-file=false``.
62+
63+
#### Configuration
64+
65+
- Git metadata: You can configure the test results by passing the `--sift-test-results-git-metadata` flag to pytest in your commandline, conftest.py file, or as an addopt in your pyproject.toml file (https://docs.pytest.org/en/stable/reference/customize.html#configuration).
66+
- Log file: You can configure the log file by passing the `--sift-test-results-log-file` flag to pytest in your commandline, conftest.py file, or as an addopt in your pyproject.toml file.
6267
6368
###### Example at top of your test file or in your conftest.py file:
6469
@@ -101,10 +106,10 @@ def test_example(report_context, step):
101106
client_has_connection,
102107
module_substep,
103108
module_substep_check_connection,
109+
pytest_addoption,
104110
pytest_runtest_makereport,
105111
report_context,
106112
report_context_check_connection,
107-
report_context_no_logging,
108113
step,
109114
step_check_connection,
110115
)
@@ -115,10 +120,10 @@ def test_example(report_context, step):
115120
"client_has_connection",
116121
"module_substep",
117122
"module_substep_check_connection",
123+
"pytest_addoption",
118124
"pytest_runtest_makereport",
119125
"report_context",
120126
"report_context_check_connection",
121-
"report_context_no_logging",
122127
"step",
123128
"step_check_connection",
124129
]

python/lib/sift_client/util/test_results/context_manager.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,29 @@
3838
logger = logging.getLogger(__name__)
3939

4040

41+
def _git_metadata() -> dict[str, str] | None:
42+
"""Return git branch and commit hash, or None if not in a git repo."""
43+
try:
44+
branch = subprocess.check_output(
45+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
46+
stderr=subprocess.DEVNULL,
47+
text=True,
48+
).strip()
49+
commit = subprocess.check_output(
50+
["git", "rev-parse", "--short", "HEAD"],
51+
stderr=subprocess.DEVNULL,
52+
text=True,
53+
).strip()
54+
repo = subprocess.check_output(
55+
["git", "remote", "get-url", "origin"],
56+
stderr=subprocess.DEVNULL,
57+
text=True,
58+
).strip()
59+
return {"git_repo": repo, "git_branch": branch, "git_commit": commit}
60+
except Exception:
61+
return None
62+
63+
4164
class ReportContext(AbstractContextManager):
4265
"""Context manager for a new TestReport. See usage example in __init__.py."""
4366

@@ -59,6 +82,7 @@ def __init__(
5982
system_operator: str | None = None,
6083
test_case: str | None = None,
6184
log_file: str | Path | bool | None = None,
85+
include_git_metadata: bool = False,
6286
):
6387
"""Initialize a new report context.
6488
@@ -70,6 +94,7 @@ def __init__(
7094
test_case: The name of the test case. Will default to the basename of the file containing the test if not provided.
7195
log_file: If True, create a temp log file. If a path, use that path.
7296
All create/update operations will be logged to this file.
97+
include_git_metadata: If True, include git metadata in the report.
7398
"""
7499
self.client = client
75100
self.step_is_open = False
@@ -99,6 +124,7 @@ def __init__(
99124
end_time=datetime.now(timezone.utc),
100125
status=TestStatus.IN_PROGRESS,
101126
system_operator=system_operator,
127+
metadata=_git_metadata() if include_git_metadata else None, # type: ignore
102128
)
103129
self.report = client.test_results.create(create, log_file=self.log_file)
104130

python/lib/sift_client/util/test_results/pytest_util.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,39 @@
1616
REPORT_CONTEXT: ReportContext | None = None
1717

1818

19+
def pytest_addoption(parser: pytest.Parser) -> None:
20+
"""Register Sift-specific command-line options."""
21+
parser.addoption(
22+
"--sift-test-results-log-file",
23+
default=None,
24+
help="Path to write the Sift test result log file. "
25+
"Use 'true' (default) to auto-create a temp file, "
26+
"False, 'false', or 'none' to disable logging, "
27+
"or a file path to write to a specific location.",
28+
)
29+
parser.addoption(
30+
"--sift-test-results-git-metadata",
31+
action="store_true",
32+
default=True,
33+
help="Include git metadata in the Sift test results.",
34+
)
35+
36+
37+
def _resolve_log_file(pytestconfig: pytest.Config | None) -> str | Path | bool | None:
38+
"""Determine log_file value from --sift-test-results-log-file option."""
39+
raw: str | None = None
40+
if pytestconfig is not None:
41+
raw = pytestconfig.getoption("--sift-test-results-log-file", default=None)
42+
if raw is None:
43+
return True
44+
lower = str(raw).lower()
45+
if lower in ("true", "1"):
46+
return True
47+
if lower in ("false", "none"):
48+
return None
49+
return Path(raw)
50+
51+
1952
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
2053
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo[Any]):
2154
"""You should import this hook to capture any AssertionErrors that occur during the test. If not included, any assert failures in a test will not automatically fail the step."""
@@ -34,7 +67,7 @@ def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo[Any]):
3467
def _report_context_impl(
3568
sift_client: SiftClient,
3669
request: pytest.FixtureRequest,
37-
log_file: str | Path | bool | None = True,
70+
pytestconfig: pytest.Config | None = None,
3871
) -> Generator[ReportContext | None, None, None]:
3972
test_path = Path(request.config.invocation_params.args[0])
4073
base_name = (
@@ -43,11 +76,18 @@ def _report_context_impl(
4376
else "pytest " + " ".join(request.config.invocation_params.args)
4477
)
4578
test_case = test_path if test_path.exists() else base_name
79+
log_file = _resolve_log_file(pytestconfig)
80+
include_git_metadata = (
81+
bool(pytestconfig.getoption("--sift-test-results-git-metadata", default=False))
82+
if pytestconfig
83+
else False
84+
)
4685
with ReportContext(
4786
sift_client,
4887
name=f"{base_name} {datetime.now(timezone.utc).isoformat()}",
4988
test_case=str(test_case),
5089
log_file=log_file,
90+
include_git_metadata=include_git_metadata,
5191
) as context:
5292
# Set a global so we can access this in pytest hooks.
5393
global REPORT_CONTEXT
@@ -57,18 +97,14 @@ def _report_context_impl(
5797

5898
@pytest.fixture(scope="session", autouse=True)
5999
def report_context(
60-
sift_client: SiftClient, request: pytest.FixtureRequest
100+
sift_client: SiftClient, request: pytest.FixtureRequest, pytestconfig: pytest.Config
61101
) -> Generator[ReportContext | None, None, None]:
62-
"""Create a report context for the session."""
63-
yield from _report_context_impl(sift_client, request, log_file=True)
102+
"""Create a report context for the session.
64103
65-
66-
@pytest.fixture(scope="session", autouse=True)
67-
def report_context_no_logging(
68-
sift_client: SiftClient, request: pytest.FixtureRequest
69-
) -> Generator[ReportContext | None, None, None]:
70-
"""Create a report context for the session with logging disabled."""
71-
yield from _report_context_impl(sift_client, request, log_file=None)
104+
The log file destination is controlled by ``--sift-test-results-log-file``.
105+
Defaults to a temp file when not set.
106+
"""
107+
yield from _report_context_impl(sift_client, request, pytestconfig=pytestconfig)
72108

73109

74110
def _step_impl(
@@ -127,11 +163,14 @@ def client_has_connection(sift_client):
127163

128164
@pytest.fixture(scope="session", autouse=True)
129165
def report_context_check_connection(
130-
sift_client: SiftClient, client_has_connection: bool, request: pytest.FixtureRequest
166+
sift_client: SiftClient,
167+
client_has_connection: bool,
168+
request: pytest.FixtureRequest,
169+
pytestconfig: pytest.Config,
131170
) -> Generator[ReportContext | None, None, None]:
132171
"""Create a report context for the session. Doesn't run if the client has no connection to the Sift server."""
133172
if client_has_connection:
134-
yield from _report_context_impl(sift_client, request)
173+
yield from _report_context_impl(sift_client, request, pytestconfig=pytestconfig)
135174
else:
136175
yield None
137176

0 commit comments

Comments
 (0)