mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-07 21:50:39 +01:00
chore: convert lifecycle-free fixture-factories to plain functions (#12462)
#### Description - Add the fixture-vs-function rule to `.claude/rules/pytest.md`: a fixture earns its indirection only by owning setup/teardown (`yield` + cleanup) or provisioning a resource; a stateless action or lookup is a plain importable function in the matching `tests/fixtures/` module taking `signoz`/`token` as ordinary arguments. - Apply it to the three fixture-factories introduced in #12460 that have no lifecycle: `delete_all_dashboards` (renamed from `wipe_all_dashboards`) and `run_query_case` are now plain functions, their modules deregistered from `pytest_plugins`, and all call sites updated. - Generalize `Metrics.load_from_file` with a `label_substitutions` parameter (placeholder rewriting, e.g. `__START_TIME__` → runtime ISO string) and drop the bespoke `load_pods_metrics`, which duplicated the base-time rebase logic — `02_pods.py` now loads JSONL the same way as every other inframonitoring suite file. Follow-up promised in https://github.com/SigNoz/signoz/pull/12460#discussion_r3737099384.
This commit is contained in:
@@ -9,6 +9,7 @@ For the Python integration suite under `tests/`. Setup, running, and suite layou
|
||||
|
||||
- **No `_`-prefixed helper functions in test modules — this is the rule that matters most.** A reader must be able to see what a test does in its body alone, without chasing private helpers that scatter the meaning across the file. Inline the logic: an expression, a comprehension, a few repeated lines are all fine — repetition across tests is cheaper than indirection. When several tests genuinely share non-trivial setup or assertions, that is what fixtures are for — in `tests/fixtures/`, see the next rule. A module-level `_helper()` is never the answer.
|
||||
- **Fixtures live in `tests/fixtures/` — never under `integration/tests/`.** Not in test modules, not in suite `conftest.py` files. `tests/fixtures/` is the shared library (auth, signoz, clickhouse, logs/metrics/traces seeding, …): reuse what's there before writing anything new; when a new fixture is genuinely needed, add it to the matching `tests/fixtures/` module and register new modules in `tests/conftest.py` `pytest_plugins`. **The one exception: SigNoz-level fixtures in a suite's `conftest.py`.** A suite that needs its own SigNoz spun up with different envs (`create_signoz`/`create_migrator` with `env_overrides` + `cache_key` — e.g. basepath, metricreduction, querier_json_body) keeps that in its `conftest.py`; that is always okay.
|
||||
- **Fixture only when there is a lifecycle; otherwise a plain function.** A fixture earns its indirection by owning setup/teardown (`yield` + cleanup — `insert_metrics` truncating on teardown) or by provisioning a resource (containers, SigNoz instances). A stateless action or lookup (`create_saved_view`, `find_saved_view_by_name`, wiping a resource list) is a plain importable function in the matching `tests/fixtures/` module, taking `signoz`/`token` as ordinary arguments — never wrap a plain callable in a fixture-factory just to inject `signoz`.
|
||||
- **Fixtures own their cleanup.** When a test needs seeded state, put the seed + cleanup pair in a fixture (`yield`, then tear down) so tests in the same suite don't interfere — the pattern `insert_metrics` sets: yield a callable, truncate on teardown.
|
||||
- **Fixture-factory over indirect parametrization.** A fixture that yields a callable (e.g. `insert_metrics(metrics)`) is clearer than `@pytest.mark.parametrize(..., indirect=True)` + `request.param` — the value is an explicit argument, not resolved by magic.
|
||||
- **Skip at collection, not inside the test body.** Use `pytest.param(..., marks=pytest.mark.skip(reason="…"))` so a skipped case shows as SKIPPED-with-reason **and** short-circuits before its fixtures run (no environment spin-up for a test that won't execute).
|
||||
|
||||
@@ -34,9 +34,6 @@ pytest_plugins = [
|
||||
"fixtures.role",
|
||||
"fixtures.savedview",
|
||||
"fixtures.seed_golden_dataset",
|
||||
"fixtures.dashboards",
|
||||
"fixtures.inframonitoring",
|
||||
"fixtures.querier",
|
||||
]
|
||||
|
||||
|
||||
|
||||
36
tests/fixtures/dashboards.py
vendored
36
tests/fixtures/dashboards.py
vendored
@@ -1,7 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
@@ -12,25 +10,21 @@ DASHBOARDS_BASE_URL = "/api/v2/dashboards"
|
||||
MAX_LIST_LIMIT = 200
|
||||
|
||||
|
||||
@pytest.fixture(name="wipe_all_dashboards", scope="function")
|
||||
def wipe_all_dashboards(signoz: types.SigNoz) -> Callable[[str], None]:
|
||||
def _wipe_all_dashboards(token: str) -> None:
|
||||
while True:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{DASHBOARDS_BASE_URL}?limit={MAX_LIST_LIMIT}"),
|
||||
def delete_all_dashboards(signoz: types.SigNoz, token: str) -> None:
|
||||
while True:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{DASHBOARDS_BASE_URL}?limit={MAX_LIST_LIMIT}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
dashboards = response.json()["data"]["dashboards"]
|
||||
if not dashboards:
|
||||
return
|
||||
for dashboard in dashboards:
|
||||
del_res = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DASHBOARDS_BASE_URL}/{dashboard['id']}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
dashboards = response.json()["data"]["dashboards"]
|
||||
if not dashboards:
|
||||
return
|
||||
for dashboard in dashboards:
|
||||
del_res = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DASHBOARDS_BASE_URL}/{dashboard['id']}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert del_res.status_code == HTTPStatus.NO_CONTENT, del_res.text
|
||||
|
||||
return _wipe_all_dashboards
|
||||
assert del_res.status_code == HTTPStatus.NO_CONTENT, del_res.text
|
||||
|
||||
54
tests/fixtures/inframonitoring.py
vendored
54
tests/fixtures/inframonitoring.py
vendored
@@ -1,15 +1,3 @@
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from fixtures.fs import get_testdata_file_path
|
||||
from fixtures.metrics import Metrics
|
||||
from fixtures.time import parse_timestamp
|
||||
|
||||
START_TIME_PLACEHOLDER = "__START_TIME__"
|
||||
|
||||
# All 18 PodCountsByStatus buckets (camelCase, matches inframonitoringtypes.PodCountsByStatus / the API response).
|
||||
STATUS_BUCKETS = (
|
||||
"pending",
|
||||
@@ -60,45 +48,3 @@ def expected_status_counts(**nonzero: int) -> dict:
|
||||
counts = {bucket: 0 for bucket in STATUS_BUCKETS}
|
||||
counts.update(nonzero)
|
||||
return counts
|
||||
|
||||
|
||||
@pytest.fixture(name="load_pods_metrics", scope="function")
|
||||
def load_pods_metrics() -> Callable[..., list[Metrics]]:
|
||||
"""Load pod metrics JSONL with optional k8s.pod.start_time substitution.
|
||||
|
||||
Mirrors Metrics.load_from_file's base_time rebase logic but adds a hook
|
||||
for the start_time label. Lines carrying ``k8s.pod.start_time =
|
||||
__START_TIME__`` get rewritten to ``start_time.isoformat()`` before
|
||||
construction, ensuring podAge is deterministic across runs.
|
||||
"""
|
||||
|
||||
def _load_pods_metrics(
|
||||
file_relpath: str,
|
||||
base_time: datetime,
|
||||
start_time: datetime | None = None,
|
||||
) -> list[Metrics]:
|
||||
path = get_testdata_file_path(file_relpath)
|
||||
start_time_iso = start_time.isoformat() if start_time else None
|
||||
rows = []
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
data = json.loads(line)
|
||||
labels = data.get("labels", {})
|
||||
if start_time_iso and labels.get("k8s.pod.start_time") == START_TIME_PLACEHOLDER:
|
||||
labels["k8s.pod.start_time"] = start_time_iso
|
||||
rows.append(data)
|
||||
if not rows:
|
||||
return []
|
||||
earliest = min(parse_timestamp(r["timestamp"]) for r in rows)
|
||||
offset = base_time - earliest
|
||||
metrics = []
|
||||
for r in rows:
|
||||
ts = parse_timestamp(r["timestamp"]) + offset
|
||||
r["timestamp"] = ts.isoformat()
|
||||
metrics.append(Metrics.from_dict(r))
|
||||
return metrics
|
||||
|
||||
return _load_pods_metrics
|
||||
|
||||
12
tests/fixtures/metrics.py
vendored
12
tests/fixtures/metrics.py
vendored
@@ -374,6 +374,7 @@ class Metrics(ABC):
|
||||
file_path: str,
|
||||
base_time: datetime.datetime | None = None,
|
||||
metric_name_override: str | None = None,
|
||||
label_substitutions: dict[str, str] | None = None,
|
||||
) -> list["Metrics"]:
|
||||
"""
|
||||
Load metrics from a JSONL file.
|
||||
@@ -385,6 +386,9 @@ class Metrics(ABC):
|
||||
base_time: If provided, all timestamps are shifted so the earliest
|
||||
timestamp in the file maps to base_time
|
||||
metric_name_override: If provided, overrides metric_name for all metrics
|
||||
label_substitutions: If provided, any label whose value equals a key is
|
||||
rewritten to that key's value (placeholder substitution,
|
||||
e.g. {"__START_TIME__": start_time.isoformat()})
|
||||
"""
|
||||
data_list = []
|
||||
with open(file_path, encoding="utf-8") as f:
|
||||
@@ -392,7 +396,13 @@ class Metrics(ABC):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
data_list.append(json.loads(line))
|
||||
data = json.loads(line)
|
||||
if label_substitutions:
|
||||
labels = data.get("labels", {})
|
||||
for key, value in labels.items():
|
||||
if value in label_substitutions:
|
||||
labels[key] = label_substitutions[value]
|
||||
data_list.append(data)
|
||||
|
||||
if not data_list:
|
||||
return []
|
||||
|
||||
84
tests/fixtures/querier.py
vendored
84
tests/fixtures/querier.py
vendored
@@ -1,10 +1,8 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
@@ -1113,49 +1111,45 @@ def make_scalar_query_request(
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="run_query_case", scope="function")
|
||||
def run_query_case(signoz: types.SigNoz) -> Callable[[str, datetime, dict[str, Any]], None]:
|
||||
def _run_query_case(token: str, now: datetime, case: dict[str, Any]) -> None:
|
||||
start_ms = case.get("startMs", int((now - timedelta(seconds=10)).timestamp() * 1000))
|
||||
end_ms = case.get("endMs", int(now.timestamp() * 1000))
|
||||
def run_query_case(signoz: types.SigNoz, token: str, now: datetime, case: dict[str, Any]) -> None:
|
||||
start_ms = case.get("startMs", int((now - timedelta(seconds=10)).timestamp() * 1000))
|
||||
end_ms = case.get("endMs", int(now.timestamp() * 1000))
|
||||
|
||||
if case["requestType"] == "raw":
|
||||
query = build_raw_query(
|
||||
name=case["name"],
|
||||
signal="logs",
|
||||
filter_expression=case.get("expression"),
|
||||
order=case.get("order") or [build_order_by("timestamp", "desc")],
|
||||
limit=case.get("limit", 100),
|
||||
step_interval=case.get("stepInterval") or 60,
|
||||
)
|
||||
else:
|
||||
aggregation = case.get("aggregation")
|
||||
if aggregation and not isinstance(aggregation, list):
|
||||
aggregations = [build_aggregation(aggregation)]
|
||||
elif aggregation:
|
||||
aggregations = aggregation
|
||||
else:
|
||||
aggregations = []
|
||||
query = build_scalar_query(
|
||||
name=case["name"],
|
||||
signal="logs",
|
||||
aggregations=aggregations,
|
||||
group_by=case.get("groupBy"),
|
||||
order=case.get("order"),
|
||||
limit=case.get("limit", 100),
|
||||
filter_expression=case.get("expression"),
|
||||
step_interval=case.get("stepInterval") or 60,
|
||||
)
|
||||
|
||||
response = make_query_request(
|
||||
signoz=signoz,
|
||||
token=token,
|
||||
start_ms=start_ms,
|
||||
end_ms=end_ms,
|
||||
queries=[query],
|
||||
request_type=case["requestType"],
|
||||
if case["requestType"] == "raw":
|
||||
query = build_raw_query(
|
||||
name=case["name"],
|
||||
signal="logs",
|
||||
filter_expression=case.get("expression"),
|
||||
order=case.get("order") or [build_order_by("timestamp", "desc")],
|
||||
limit=case.get("limit", 100),
|
||||
step_interval=case.get("stepInterval") or 60,
|
||||
)
|
||||
else:
|
||||
aggregation = case.get("aggregation")
|
||||
if aggregation and not isinstance(aggregation, list):
|
||||
aggregations = [build_aggregation(aggregation)]
|
||||
elif aggregation:
|
||||
aggregations = aggregation
|
||||
else:
|
||||
aggregations = []
|
||||
query = build_scalar_query(
|
||||
name=case["name"],
|
||||
signal="logs",
|
||||
aggregations=aggregations,
|
||||
group_by=case.get("groupBy"),
|
||||
order=case.get("order"),
|
||||
limit=case.get("limit", 100),
|
||||
filter_expression=case.get("expression"),
|
||||
step_interval=case.get("stepInterval") or 60,
|
||||
)
|
||||
assert response.status_code == 200, f"HTTP {response.status_code} for case '{case['name']}': {response.text}"
|
||||
assert case["validate"](response), f"Validation failed for case '{case['name']}': {response.json()}"
|
||||
|
||||
return _run_query_case
|
||||
response = make_query_request(
|
||||
signoz=signoz,
|
||||
token=token,
|
||||
start_ms=start_ms,
|
||||
end_ms=end_ms,
|
||||
queries=[query],
|
||||
request_type=case["requestType"],
|
||||
)
|
||||
assert response.status_code == 200, f"HTTP {response.status_code} for case '{case['name']}': {response.text}"
|
||||
assert case["validate"](response), f"Validation failed for case '{case['name']}': {response.json()}"
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
import requests
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.dashboards import delete_all_dashboards
|
||||
from fixtures.metrics import Metrics
|
||||
from fixtures.types import Operation, SigNoz
|
||||
|
||||
@@ -602,7 +603,6 @@ def test_dashboard_v2_lifecycle( # pylint: disable=too-many-locals,too-many-sta
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
wipe_all_dashboards: Callable[[str], None],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
@@ -610,7 +610,7 @@ def test_dashboard_v2_lifecycle( # pylint: disable=too-many-locals,too-many-sta
|
||||
# runs, so start from a clean slate: delete every dashboard (which also clears
|
||||
# pins via the delete cascade). This test then owns the whole dashboard space
|
||||
# and asserts on global counts.
|
||||
wipe_all_dashboards(token)
|
||||
delete_all_dashboards(signoz, token)
|
||||
|
||||
dashboard_requests = [
|
||||
(
|
||||
@@ -1247,7 +1247,6 @@ def test_dashboard_v2_pin_limit(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
wipe_all_dashboards: Callable[[str], None],
|
||||
):
|
||||
max_pinned = 10
|
||||
|
||||
@@ -1255,7 +1254,7 @@ def test_dashboard_v2_pin_limit(
|
||||
|
||||
# Wipe the dashboard space (see lifecycle) so the per-user pin cap this test
|
||||
# asserts against starts empty — deleting dashboards clears their pins.
|
||||
wipe_all_dashboards(token)
|
||||
delete_all_dashboards(signoz, token)
|
||||
|
||||
ids: list[str] = []
|
||||
for i in range(max_pinned + 1):
|
||||
@@ -1343,13 +1342,12 @@ def test_dashboard_v2_like_escaping(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
wipe_all_dashboards: Callable[[str], None],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Wipe the dashboard space (see lifecycle) so the filter assertions run
|
||||
# against only the dashboards this test creates.
|
||||
wipe_all_dashboards(token)
|
||||
delete_all_dashboards(signoz, token)
|
||||
|
||||
dashboard_requests = [
|
||||
("esc-pct", "Cost 50% Report"),
|
||||
@@ -1420,7 +1418,6 @@ def test_dashboard_v2_get_by_metric_name(
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[list[Metrics]], None],
|
||||
wipe_all_dashboards: Callable[[str], None],
|
||||
) -> None:
|
||||
"""The v3 endpoint shortlists dashboards via a coarse data prefilter, then
|
||||
confirms matches by parsing the typed v2 panels. It must find the metric in
|
||||
@@ -1428,7 +1425,7 @@ def test_dashboard_v2_get_by_metric_name(
|
||||
the metric appears only in panel names (the prefilter matches but the parse
|
||||
rejects it)."""
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
wipe_all_dashboards(token)
|
||||
delete_all_dashboards(signoz, token)
|
||||
|
||||
target_metric = "system.network.dropped"
|
||||
decoy_metric = "system.network.io"
|
||||
|
||||
@@ -9,11 +9,14 @@ from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.fs import get_testdata_file_path
|
||||
from fixtures.inframonitoring import STATUS_BUCKETS, STATUS_TO_BUCKET
|
||||
from fixtures.metrics import Metrics
|
||||
from fixtures.querier import compare_values, get_all_warnings
|
||||
|
||||
ENDPOINT = "/api/v2/infra_monitoring/pods"
|
||||
|
||||
# Placeholder in JSONL labels that gets substituted with a runtime ISO string.
|
||||
# Placeholder in JSONL labels that gets substituted with a runtime ISO string,
|
||||
# keeping podAge deterministic across runs.
|
||||
START_TIME_PLACEHOLDER = "__START_TIME__"
|
||||
|
||||
|
||||
def test_pods_accuracy(
|
||||
@@ -21,7 +24,6 @@ def test_pods_accuracy(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""Seed 2 pods x 7 metrics; assert response shape/contract + exact per-pod
|
||||
metric values, podAge, and podCountsByStatus against precomputed expected
|
||||
@@ -29,10 +31,10 @@ def test_pods_accuracy(
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
start_time = now - timedelta(minutes=10)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_value_accuracy.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_value_accuracy.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
start_time=start_time,
|
||||
label_substitutions={START_TIME_PLACEHOLDER: start_time.isoformat()},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -150,7 +152,6 @@ def test_pods_warnings(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
case: dict,
|
||||
) -> None:
|
||||
"""A never-ingested metric surfaces a non-blocking warning (200 + data), not a
|
||||
@@ -160,8 +161,8 @@ def test_pods_warnings(
|
||||
once, for hosts, in 01_hosts.py.)"""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
f"inframonitoring/{case['dataset']}",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path(f"inframonitoring/{case['dataset']}"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -249,7 +250,6 @@ def test_pods_filter(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
expression: str,
|
||||
expected_pods: set,
|
||||
) -> None:
|
||||
@@ -269,8 +269,8 @@ def test_pods_filter(
|
||||
}
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_filter_dataset.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_filter_dataset.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -310,7 +310,6 @@ def test_pods_filter_invalid(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
expression: str,
|
||||
err_substr,
|
||||
) -> None:
|
||||
@@ -318,8 +317,8 @@ def test_pods_filter_invalid(
|
||||
400 invalid_input with structured errors."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_filter_dataset.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_filter_dataset.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -357,7 +356,6 @@ def test_pods_groupby(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
group_key: str,
|
||||
expected_groups: set,
|
||||
) -> None:
|
||||
@@ -367,8 +365,8 @@ def test_pods_groupby(
|
||||
"""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_groupby.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_groupby.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -412,15 +410,14 @@ def test_pods_pagination(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""Pagination: per-page len matches min(limit, total-offset), total invariant,
|
||||
pages cover the full set with no overlap. The final offset is beyond total:
|
||||
it returns empty records while total still reflects dataset size."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_pagination.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_pagination.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -475,7 +472,6 @@ def test_pods_orderby( # pylint: disable=too-many-arguments,too-many-positional
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
column: str,
|
||||
record_field,
|
||||
direction: str,
|
||||
@@ -484,8 +480,8 @@ def test_pods_orderby( # pylint: disable=too-many-arguments,too-many-positional
|
||||
sort) and records come back sorted by the requested column."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_orderby.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_orderby.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -610,7 +606,6 @@ def test_pods_status_list_mode(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
pod_name: str,
|
||||
expected_status: str,
|
||||
) -> None:
|
||||
@@ -631,8 +626,8 @@ def test_pods_status_list_mode(
|
||||
"""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -679,7 +674,6 @@ def test_pods_restarts_list_mode(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
pod_name: str,
|
||||
expected_restarts: int,
|
||||
) -> None:
|
||||
@@ -688,8 +682,8 @@ def test_pods_restarts_list_mode(
|
||||
series -> -1 no-data sentinel (kubectl would show 0)."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -719,7 +713,6 @@ def test_pods_status_latest_wins(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""A stale container reason from an old incarnation must not win. trans-p's
|
||||
first incarnation (container.id=aaa) reported CrashLoopBackOff, then it
|
||||
@@ -728,8 +721,8 @@ def test_pods_status_latest_wins(
|
||||
ignored via argMax-by-latest-timestamp per (pod, container, reason)."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases_transition.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases_transition.jsonl"),
|
||||
base_time=now - timedelta(minutes=8),
|
||||
)
|
||||
)
|
||||
@@ -758,7 +751,6 @@ def test_pods_restarts_latest_wins(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""restartCount is cumulative per container. Across incarnations
|
||||
(container.id=aaa reported 1, then container.id=bbb reported 5) podRestarts
|
||||
@@ -766,8 +758,8 @@ def test_pods_restarts_latest_wins(
|
||||
not be double-counted."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases_transition.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases_transition.jsonl"),
|
||||
base_time=now - timedelta(minutes=8),
|
||||
)
|
||||
)
|
||||
@@ -796,7 +788,6 @@ def test_pods_status_grouped_mode(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""groupBy=[k8s.namespace.name] aggregates each pod's display status across
|
||||
ns-mixed. podStatus is no-data (no single pod identifies the group). Seeded
|
||||
@@ -804,8 +795,8 @@ def test_pods_status_grouped_mode(
|
||||
g-fail-1 Error, g-fail-2 Evicted, g-pend-1 Pending."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases_grouped.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases_grouped.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -853,14 +844,13 @@ def test_pods_restarts_grouped_mode(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""Grouped podRestarts is the sum of restarts across all pods in the group.
|
||||
In ns-mixed only g-run-2 has restarts (3); all others 0 -> group total 3."""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_phases_grouped.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_phases_grouped.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
@@ -896,7 +886,6 @@ def test_pods_status_missing_metric_warning(
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token,
|
||||
insert_metrics,
|
||||
load_pods_metrics,
|
||||
) -> None:
|
||||
"""When the status metrics were never ingested, the status query is gated
|
||||
off: a warning naming the missing metric(s) is surfaced, podStatus is the
|
||||
@@ -904,8 +893,8 @@ def test_pods_status_missing_metric_warning(
|
||||
seeds only k8s.pod.cpu.usage.)"""
|
||||
now = datetime.now(tz=UTC).replace(microsecond=0)
|
||||
insert_metrics(
|
||||
load_pods_metrics(
|
||||
"inframonitoring/pods_missing_metrics.jsonl",
|
||||
Metrics.load_from_file(
|
||||
get_testdata_file_path("inframonitoring/pods_missing_metrics.jsonl"),
|
||||
base_time=now - timedelta(minutes=4),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ from fixtures.querier import (
|
||||
get_rows,
|
||||
get_scalar_table_data,
|
||||
make_query_request,
|
||||
run_query_case,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
@@ -37,11 +38,11 @@ from fixtures.querier import (
|
||||
|
||||
|
||||
def test_primitive_path_operations(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -323,7 +324,7 @@ def test_primitive_path_operations(
|
||||
for case in cases:
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -335,13 +336,13 @@ def test_primitive_path_operations(
|
||||
|
||||
|
||||
def test_indexed_paths(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
create_json_index: Callable[[str, list[dict[str, Any]]], None],
|
||||
check_query_log: Callable[[datetime, str, Callable[[str], bool]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -494,7 +495,7 @@ def test_indexed_paths(
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
before = datetime.now(tz=UTC)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
if "check_query" in case:
|
||||
check_query_log(
|
||||
before,
|
||||
@@ -665,11 +666,11 @@ def test_select_order_by(
|
||||
|
||||
|
||||
def test_array_path_operations(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -939,7 +940,7 @@ def test_array_path_operations(
|
||||
for case in cases:
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -948,11 +949,11 @@ def test_array_path_operations(
|
||||
|
||||
|
||||
def test_array_membership_operations(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -1085,7 +1086,7 @@ def test_array_membership_operations(
|
||||
for case in cases:
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1094,11 +1095,11 @@ def test_array_membership_operations(
|
||||
|
||||
|
||||
def test_message_searches(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -1215,7 +1216,7 @@ def test_message_searches(
|
||||
for case in cases:
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1224,11 +1225,11 @@ def test_message_searches(
|
||||
|
||||
|
||||
def test_polluted_data(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -1342,7 +1343,7 @@ def test_polluted_data(
|
||||
for case in cases:
|
||||
case.setdefault("groupBy", None)
|
||||
case.setdefault("stepInterval", None)
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1365,11 +1366,11 @@ def test_polluted_data(
|
||||
|
||||
|
||||
def test_groupby_scalar(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
export_json_types: Callable[[list[Logs]], None],
|
||||
run_query_case: Callable[[str, datetime, dict[str, Any]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=UTC)
|
||||
|
||||
@@ -1429,4 +1430,4 @@ def test_groupby_scalar(
|
||||
]
|
||||
|
||||
for case in cases:
|
||||
run_query_case(token, now, case)
|
||||
run_query_case(signoz, token, now, case)
|
||||
|
||||
Reference in New Issue
Block a user