fix(monitoring): bound Titan test metric labels
This commit is contained in:
parent
b34888abb3
commit
87c0a8b201
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from glob import glob
|
from glob import glob
|
||||||
@ -27,6 +28,8 @@ _infer_workspace_coverage_percent = _quality_helpers._infer_workspace_coverage_p
|
|||||||
_load_optional_json = _quality_helpers._load_optional_json
|
_load_optional_json = _quality_helpers._load_optional_json
|
||||||
_normalize_result_status = _quality_helpers._normalize_result_status
|
_normalize_result_status = _quality_helpers._normalize_result_status
|
||||||
|
|
||||||
|
TEST_CASE_LABEL_MAX_BYTES = 240
|
||||||
|
|
||||||
|
|
||||||
def _escape_label(value: str) -> str:
|
def _escape_label(value: str) -> str:
|
||||||
"""Escape a Prometheus label value without changing its content."""
|
"""Escape a Prometheus label value without changing its content."""
|
||||||
@ -39,6 +42,17 @@ def _label_str(labels: dict[str, str]) -> str:
|
|||||||
return "{" + ",".join(parts) + "}" if parts else ""
|
return "{" + ",".join(parts) + "}" if parts else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _bounded_test_name(value: str) -> str:
|
||||||
|
"""Keep test labels readable, unique, and safely below scraper limits."""
|
||||||
|
encoded = value.encode("utf-8")
|
||||||
|
if len(encoded) <= TEST_CASE_LABEL_MAX_BYTES:
|
||||||
|
return value
|
||||||
|
digest = hashlib.sha256(encoded).hexdigest()[:16]
|
||||||
|
suffix = f"...[sha256:{digest}]"
|
||||||
|
prefix = encoded[: TEST_CASE_LABEL_MAX_BYTES - len(suffix)]
|
||||||
|
return prefix.decode("utf-8", errors="ignore") + suffix
|
||||||
|
|
||||||
|
|
||||||
def _read_text(url: str) -> str:
|
def _read_text(url: str) -> str:
|
||||||
"""Fetch a plain-text response body from the given URL."""
|
"""Fetch a plain-text response body from the given URL."""
|
||||||
with urllib.request.urlopen(url, timeout=10) as response:
|
with urllib.request.urlopen(url, timeout=10) as response:
|
||||||
@ -121,7 +135,7 @@ def _collect_junit_cases(pattern: str) -> list[tuple[str, str]]:
|
|||||||
status = "failed"
|
status = "failed"
|
||||||
elif test_case.find("skipped") is not None:
|
elif test_case.find("skipped") is not None:
|
||||||
status = "skipped"
|
status = "skipped"
|
||||||
cases.append((full_name, status))
|
cases.append((_bounded_test_name(full_name), status))
|
||||||
return cases
|
return cases
|
||||||
|
|
||||||
|
|
||||||
@ -246,7 +260,7 @@ def _build_payload(
|
|||||||
for test_name, test_status in test_cases:
|
for test_name, test_status in test_cases:
|
||||||
labels = {
|
labels = {
|
||||||
**test_case_base_labels,
|
**test_case_base_labels,
|
||||||
"test": test_name,
|
"test": _bounded_test_name(test_name),
|
||||||
"status": test_status,
|
"status": test_status,
|
||||||
}
|
}
|
||||||
lines.append(
|
lines.append(
|
||||||
|
|||||||
@ -1632,7 +1632,7 @@ def testing_case_variable():
|
|||||||
"label": "Test Case",
|
"label": "Test Case",
|
||||||
"type": "query",
|
"type": "query",
|
||||||
"query": (
|
"query": (
|
||||||
"query_result(topk(75, count by (test) (max_over_time("
|
"query_result(bottomk(75, min by (test) (min_over_time("
|
||||||
f'platform_quality:test_case_health_rate:percent_1h{{suite=~"${{suite:regex}}",branch!="",'
|
f'platform_quality:test_case_health_rate:percent_1h{{suite=~"${{suite:regex}}",branch!="",'
|
||||||
f'branch=~"${{branch:regex}}",test!="",test!="__no_test_cases__",'
|
f'branch=~"${{branch:regex}}",test!="",test!="__no_test_cases__",'
|
||||||
f'category!~"{PLATFORM_TEST_SUPPORT_CATEGORY_REGEX}"}}'
|
f'category!~"{PLATFORM_TEST_SUPPORT_CATEGORY_REGEX}"}}'
|
||||||
|
|||||||
@ -46,6 +46,8 @@ def test_jobs_dashboard_separates_current_gate_health_from_reliability():
|
|||||||
|
|
||||||
test_variable = variables_by_name["test"]
|
test_variable = variables_by_name["test"]
|
||||||
assert test_variable["query"].startswith("query_result(")
|
assert test_variable["query"].startswith("query_result(")
|
||||||
|
assert "bottomk(75" in test_variable["query"]
|
||||||
|
assert "min_over_time" in test_variable["query"]
|
||||||
assert "platform_quality:test_case_health_rate:percent_1h" in test_variable["query"]
|
assert "platform_quality:test_case_health_rate:percent_1h" in test_variable["query"]
|
||||||
assert "platform_quality_gate_test_case_result" not in test_variable["query"]
|
assert "platform_quality_gate_test_case_result" not in test_variable["query"]
|
||||||
assert test_variable["regex"] == '/test="([^"]+)"/'
|
assert test_variable["regex"] == '/test="([^"]+)"/'
|
||||||
|
|||||||
@ -5044,7 +5044,7 @@
|
|||||||
"name": "test",
|
"name": "test",
|
||||||
"label": "Test Case",
|
"label": "Test Case",
|
||||||
"type": "query",
|
"type": "query",
|
||||||
"query": "query_result(topk(75, count by (test) (max_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
|
"query": "query_result(bottomk(75, min by (test) (min_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
|
||||||
"regex": "/test=\"([^\"]+)\"/",
|
"regex": "/test=\"([^\"]+)\"/",
|
||||||
"current": {
|
"current": {
|
||||||
"text": "All",
|
"text": "All",
|
||||||
|
|||||||
@ -5053,7 +5053,7 @@ data:
|
|||||||
"name": "test",
|
"name": "test",
|
||||||
"label": "Test Case",
|
"label": "Test Case",
|
||||||
"type": "query",
|
"type": "query",
|
||||||
"query": "query_result(topk(75, count by (test) (max_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
|
"query": "query_result(bottomk(75, min by (test) (min_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
|
||||||
"regex": "/test=\"([^\"]+)\"/",
|
"regex": "/test=\"([^\"]+)\"/",
|
||||||
"current": {
|
"current": {
|
||||||
"text": "All",
|
"text": "All",
|
||||||
|
|||||||
@ -247,6 +247,16 @@ def test_exchange_returns_result_and_enforces_response_limit():
|
|||||||
(b'{"ok":true,"status":"200","content_type":"a","body":""}', 0, "invalid evidence"),
|
(b'{"ok":true,"status":"200","content_type":"a","body":""}', 0, "invalid evidence"),
|
||||||
(b'{"ok":true,"status":200,"content_type":"a","body":"!!"}', 0, "invalid evidence"),
|
(b'{"ok":true,"status":200,"content_type":"a","body":"!!"}', 0, "invalid evidence"),
|
||||||
],
|
],
|
||||||
|
ids=[
|
||||||
|
"empty-error",
|
||||||
|
"oversized-output",
|
||||||
|
"invalid-json",
|
||||||
|
"false-result",
|
||||||
|
"array-result",
|
||||||
|
"missing-fields",
|
||||||
|
"empty-body",
|
||||||
|
"invalid-body",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def test_exchange_rejects_invalid_helper_evidence(output, returncode, match):
|
def test_exchange_rejects_invalid_helper_evidence(output, returncode, match):
|
||||||
module = _load("deadline_http")
|
module = _load("deadline_http")
|
||||||
|
|||||||
@ -141,6 +141,7 @@ def test_created_at_requires_short_timezone_aware_iso(value):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
[b"x" * (1024 * 1024 + 1), b"{}", json.dumps([None]).encode()],
|
[b"x" * (1024 * 1024 + 1), b"{}", json.dumps([None]).encode()],
|
||||||
|
ids=["oversized-input", "object-input", "null-list-input"],
|
||||||
)
|
)
|
||||||
def test_evaluate_rejects_invalid_top_level_inputs(value):
|
def test_evaluate_rejects_invalid_top_level_inputs(value):
|
||||||
module = _module("branch_evaluate_top")
|
module = _module("branch_evaluate_top")
|
||||||
|
|||||||
@ -65,6 +65,30 @@ def test_build_payload_includes_explicit_test_case_series():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_test_case_labels_are_bounded_and_collision_safe():
|
||||||
|
first = "module.test_case[" + ("x" * (4 * 1024 * 1024)) + "]"
|
||||||
|
second = first[:-2] + "y]"
|
||||||
|
|
||||||
|
first_label = publish_test_metrics._bounded_test_name(first)
|
||||||
|
second_label = publish_test_metrics._bounded_test_name(second)
|
||||||
|
payload = publish_test_metrics._build_payload(
|
||||||
|
suite="titan_iac",
|
||||||
|
status="ok",
|
||||||
|
tests={"tests": 2, "failures": 0, "errors": 0, "skipped": 0},
|
||||||
|
test_cases=[(first, "passed"), (second, "passed")],
|
||||||
|
ok_count=1,
|
||||||
|
failed_count=0,
|
||||||
|
branch="main",
|
||||||
|
build_number="1",
|
||||||
|
jenkins_job="titan-iac",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(first_label.encode("utf-8")) <= publish_test_metrics.TEST_CASE_LABEL_MAX_BYTES
|
||||||
|
assert first_label != second_label
|
||||||
|
assert "[sha256:" in first_label
|
||||||
|
assert max(len(line.encode("utf-8")) for line in payload.splitlines()) < 1024
|
||||||
|
|
||||||
|
|
||||||
def test_main_uses_reported_coverage_and_loc_without_fallback(tmp_path: Path, monkeypatch, capsys):
|
def test_main_uses_reported_coverage_and_loc_without_fallback(tmp_path: Path, monkeypatch, capsys):
|
||||||
build_dir = tmp_path / "build"
|
build_dir = tmp_path / "build"
|
||||||
build_dir.mkdir()
|
build_dir.mkdir()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user