From fa05a73aef271f7d2efda2b46bb100cdc4eb7f83 Mon Sep 17 00:00:00 2001 From: Pandey Date: Fri, 7 Aug 2026 22:23:07 +0530 Subject: [PATCH] chore: convert lifecycle-free fixture-factories to plain functions (#12462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### 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. --- .claude/rules/pytest.md | 1 + tests/conftest.py | 3 - tests/fixtures/dashboards.py | 36 ++++---- tests/fixtures/inframonitoring.py | 54 ------------ tests/fixtures/metrics.py | 12 ++- tests/fixtures/querier.py | 84 +++++++++---------- .../tests/dashboard/03_v2_dashboard.py | 13 ++- .../tests/inframonitoring/02_pods.py | 77 ++++++++--------- .../01_logs_json_body_new_qb.py | 29 +++---- 9 files changed, 119 insertions(+), 190 deletions(-) diff --git a/.claude/rules/pytest.md b/.claude/rules/pytest.md index 8021d8d095..f3032460f4 100644 --- a/.claude/rules/pytest.md +++ b/.claude/rules/pytest.md @@ -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). diff --git a/tests/conftest.py b/tests/conftest.py index 1a61cbfe07..c5f13e3798 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,9 +34,6 @@ pytest_plugins = [ "fixtures.role", "fixtures.savedview", "fixtures.seed_golden_dataset", - "fixtures.dashboards", - "fixtures.inframonitoring", - "fixtures.querier", ] diff --git a/tests/fixtures/dashboards.py b/tests/fixtures/dashboards.py index 087ea1667d..3c36121435 100644 --- a/tests/fixtures/dashboards.py +++ b/tests/fixtures/dashboards.py @@ -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 diff --git a/tests/fixtures/inframonitoring.py b/tests/fixtures/inframonitoring.py index 18f9ecb47f..1ad68258e3 100644 --- a/tests/fixtures/inframonitoring.py +++ b/tests/fixtures/inframonitoring.py @@ -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 diff --git a/tests/fixtures/metrics.py b/tests/fixtures/metrics.py index 01767631a0..9e7833062f 100644 --- a/tests/fixtures/metrics.py +++ b/tests/fixtures/metrics.py @@ -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 [] diff --git a/tests/fixtures/querier.py b/tests/fixtures/querier.py index d690bd8881..d24ccea1ae 100644 --- a/tests/fixtures/querier.py +++ b/tests/fixtures/querier.py @@ -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()}" diff --git a/tests/integration/tests/dashboard/03_v2_dashboard.py b/tests/integration/tests/dashboard/03_v2_dashboard.py index d7a3c39a0f..2363ca0dfb 100644 --- a/tests/integration/tests/dashboard/03_v2_dashboard.py +++ b/tests/integration/tests/dashboard/03_v2_dashboard.py @@ -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" diff --git a/tests/integration/tests/inframonitoring/02_pods.py b/tests/integration/tests/inframonitoring/02_pods.py index 21838afbed..d8ee2cd118 100644 --- a/tests/integration/tests/inframonitoring/02_pods.py +++ b/tests/integration/tests/inframonitoring/02_pods.py @@ -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), ) ) diff --git a/tests/integration/tests/querier_json_body/01_logs_json_body_new_qb.py b/tests/integration/tests/querier_json_body/01_logs_json_body_new_qb.py index b5e2c83f7a..4846427b6b 100644 --- a/tests/integration/tests/querier_json_body/01_logs_json_body_new_qb.py +++ b/tests/integration/tests/querier_json_body/01_logs_json_body_new_qb.py @@ -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)