From f7e318612577aad7b106a471b812f3479fb6a74f Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 26 May 2026 18:05:56 +0200 Subject: [PATCH 1/2] [debugger] Add compliance tests for probe `when` condition errors Add system tests that pin down the contract for two distinct failure paths when a dynamic-instrumentation probe's `when` condition can't be evaluated: * Invalid DSL at install time: the probe must be rejected and the tracer must emit a `status=ERROR` diagnostic so the developer knows the probe was dropped. No snapshot may be produced. * Structurally valid `when` that raises at runtime: the probe must install successfully and, when the condition raises, the tracer must emit a snapshot with `evaluationErrors[]` populated (mentioning the failing symbol) and NO captured data. These eval-error snapshots must also be rate-limited so a hot probe doesn't spam the user. The tests assert the ideal behavior; existing per-tracer deviations are recorded via manifest `bug` / `missing_feature` / `irrelevant` markers. Co-authored-by: Cursor --- manifests/agent.yml | 18 ++ manifests/dotnet.yml | 6 + manifests/golang.yml | 6 + manifests/java.yml | 22 ++ manifests/nodejs.yml | 12 + manifests/php.yml | 11 + manifests/python.yml | 9 + manifests/ruby.yml | 16 ++ .../test_debugger_condition_errors.py | 239 ++++++++++++++++++ tests/debugger/utils.py | 1 + .../probes/probe_invalid_condition_dsl.json | 25 ++ .../probes/probe_runtime_condition_error.json | 26 ++ tests/schemas/test_schemas.py | 12 + 13 files changed, 403 insertions(+) create mode 100644 tests/debugger/test_debugger_condition_errors.py create mode 100644 tests/debugger/utils/probes/probe_invalid_condition_dsl.json create mode 100644 tests/debugger/utils/probes/probe_runtime_condition_error.json diff --git a/manifests/agent.yml b/manifests/agent.yml index e3fbf358fb1..25f4821fe8f 100644 --- a/manifests/agent.yml +++ b/manifests/agent.yml @@ -7,6 +7,24 @@ manifest: tests/appsec/smoke_tests/test_apm_standalone.py: - component_version: "<7.77.0-0" declaration: irrelevant (APM Standalone option was added in 7.77.0) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - component_version: "<7.77.1" + weblog_declaration: + chi: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + echo: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + gin: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + net-http: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + net-http-orchestrion: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + uds-echo: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - component_version: "<7.77.1" + weblog_declaration: + chi: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + echo: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + gin: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + net-http: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + net-http-orchestrion: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) + uds-echo: irrelevant (Go DI requires datadog-agent >= 7.77.1 for the `when` clause) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_log_line_snapshot: - component_version: ">=7.79.0-devel" weblog_declaration: diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 4a2f1479cfe..59a7e5fe2c6 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -598,6 +598,12 @@ manifest: weblog: [uds, poc] - declaration: missing_feature (Not yet implemented) excluded_weblog: [uds, poc] + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL::test_invalid_condition_dsl_probe_rejected: missing_feature (Dotnet compiles probe conditions lazily on first evaluation, so there is no install-time validation step to reject the probe.) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: v2.53.0 + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_emits_error_only_snapshot + : bug (DEBUG-5699) + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_rate_limited + : bug (DEBUG-5703) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: - weblog_declaration: "*": v2.50.0 diff --git a/manifests/golang.yml b/manifests/golang.yml index 258bca4a059..5de37f3c028 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -823,6 +823,12 @@ manifest: tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions: v2.2.3 tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_complex_capture_expressions: missing_feature (index expression not yet supported in Go system-probe) tests/debugger/test_debugger_code_origins.py::Test_Debugger_Code_Origins: missing_feature (feature not implemented) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: v2.2.3 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: v2.2.3 + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_emits_error_only_snapshot + : bug (DEBUG-5700) + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_probe_installs + : bug (DEBUG-5710) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: missing_feature (feature not implemented) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_firsthit: missing_feature (Implemented only for dotnet) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_outofmemory: missing_feature (Implemented only for dotnet) diff --git a/manifests/java.yml b/manifests/java.yml index 2f1f7bdc047..abf749c6c76 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -2893,6 +2893,28 @@ manifest: - weblog_declaration: "*": missing_feature (Implemented for spring-mvc) spring-boot: v0.0.0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - weblog_declaration: + "*": missing_feature + spring-boot: v1.38.0 + spring-boot-jetty: v1.38.0 + spring-boot-openliberty: v1.38.0 + spring-boot-payara: v1.38.0 + spring-boot-undertow: v1.38.0 + spring-boot-wildfly: v1.38.0 + uds-spring-boot: v1.38.0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - weblog_declaration: + "*": missing_feature + spring-boot: v1.38.0 + spring-boot-jetty: v1.38.0 + spring-boot-openliberty: v1.38.0 + spring-boot-payara: v1.38.0 + spring-boot-undertow: v1.38.0 + spring-boot-wildfly: v1.38.0 + uds-spring-boot: v1.38.0 + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_rate_limited + : bug (DEBUG-5704) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: - weblog_declaration: "*": missing_feature diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 5e578714b46..088cf073509 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -1549,6 +1549,18 @@ manifest: fastify: *ref_5_24_0 nextjs: missing_feature uds-express4: *ref_5_54_0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - weblog_declaration: + "*": *ref_5_46_0 + nextjs: irrelevant + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - weblog_declaration: + "*": *ref_5_46_0 + nextjs: irrelevant + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_emits_error_only_snapshot + : bug (DEBUG-5701) + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_rate_limited + : bug (DEBUG-5705) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: missing_feature tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_firsthit: missing_feature (Implemented only for dotnet) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_outofmemory: missing_feature (Implemented only for dotnet) diff --git a/manifests/php.yml b/manifests/php.yml index 28e9cb330d2..4e14b569a44 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -547,6 +547,17 @@ manifest: tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Line_Capture_Expressions: missing_feature (Not yet implemented) tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions: v1.19.0 tests/debugger/test_debugger_code_origins.py::Test_Debugger_Code_Origins::test_code_origin_entry_present: irrelevant (HTTP entry spans in PHP-FPM/Apache are C-level; execute stack is empty at span close time so no user frames are captured) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - weblog_declaration: + "*": missing_feature + apache-mod-8.0: v1.13.0+4663b2fa7c20c6920f347d059b57dc2a419cb7f7 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL::test_invalid_condition_dsl_probe_rejected: bug (DEBUG-5708) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - weblog_declaration: + "*": missing_feature + apache-mod-8.0: v1.13.0+4663b2fa7c20c6920f347d059b57dc2a419cb7f7 + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_rate_limited + : bug (DEBUG-5706) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: v1.19.0 tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_firsthit: missing_feature (Implemented only for dotnet) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_outofmemory: missing_feature (Implemented only for dotnet) diff --git a/manifests/python.yml b/manifests/python.yml index c320776ad7f..0a51b050331 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -1142,6 +1142,15 @@ manifest: - weblog_declaration: "*": missing_feature *flask: v2.11.0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - weblog_declaration: + "*": missing_feature + *flask: v2.11.0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL::test_invalid_condition_dsl_probe_rejected: bug (DEBUG-5709) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - weblog_declaration: + "*": missing_feature + *flask: v2.11.0 tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: - weblog_declaration: "*": missing_feature diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 5ae4dcd028a..c37bfe60bce 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -1639,6 +1639,22 @@ manifest: rails80: missing_feature (feature not implemented) uds-rails: missing_feature (feature not implemented) tests/debugger/test_debugger_code_origins.py::Test_Debugger_Code_Origins::test_code_origin_entry_present: missing_feature (Not yet implemented) + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Invalid_Condition_DSL: + - weblog_declaration: + "*": irrelevant + rails72: v2.22.0 + rails80: v2.22.0 + uds-rails: v2.22.0 + tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error: + - weblog_declaration: + "*": irrelevant + rails72: v2.22.0 + rails80: v2.22.0 + uds-rails: v2.22.0 + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_emits_error_only_snapshot + : bug (DEBUG-5702) + ? tests/debugger/test_debugger_condition_errors.py::Test_Debugger_Runtime_Condition_Error::test_runtime_condition_error_rate_limited + : bug (DEBUG-5707) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: missing_feature (feature not implemented) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_firsthit: missing_feature (Implemented only for dotnet) tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_outofmemory: missing_feature (Implemented only for dotnet) diff --git a/tests/debugger/test_debugger_condition_errors.py b/tests/debugger/test_debugger_condition_errors.py new file mode 100644 index 00000000000..c5dc8af6b27 --- /dev/null +++ b/tests/debugger/test_debugger_condition_errors.py @@ -0,0 +1,239 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2021 Datadog, Inc. + +"""Compliance tests: a probe with a broken ``when`` condition must not produce probe data.""" + +import time + +import tests.debugger.utils as debugger +from utils import context, features, scenarios + + +def _capture_point_contains_data(point: object) -> bool: + """Return True iff a single capture point (entry / return / one line entry) contains data.""" + if not isinstance(point, dict): + return False + for key in ("arguments", "locals", "staticFields"): + if point.get(key): + return True + return bool(point.get("throwable")) + + +def _captures_contain_data(captures: object) -> bool: + """Return True iff the ``captures`` field of a snapshot contains any captured probe data. + + A snapshot whose ``captures`` is missing, ``None``, or only contains empty + ``entry`` / ``return`` / ``lines`` sub-structures (i.e. no captured arguments, + locals, static fields, or throwable) returns False -- it is an empty captures + container and acceptable as part of an evaluation-error snapshot. + """ + if not isinstance(captures, dict): + return False + for key, value in captures.items(): + # `entry` and `return` map directly to a capture-point dict. + # `lines` maps line-numbers to capture-point dicts, so descend one level. + if key == "lines" and isinstance(value, dict): + if any(_capture_point_contains_data(v) for v in value.values()): + return True + elif _capture_point_contains_data(value): + return True + return False + + +class _ConditionTestBase(debugger.BaseDebuggerTest): + """Shared base for the condition-error compliance tests.""" + + def _convert_to_line_probe_if_needed(self, probe: dict) -> None: + """In-place: rewrite a LogProbe method probe to a line probe for tracers that need it.""" + if context.library.name != "nodejs": + return + where = probe["where"] + where.pop("methodName", None) + where["typeName"] = None + where["sourceFile"] = "ACTUAL_SOURCE_FILE" + where["lines"] = self.method_and_language_to_line_number("LogProbe", "nodejs") + + +@features.debugger_expression_language +@scenarios.debugger_probes_snapshot +class Test_Debugger_Invalid_Condition_DSL(_ConditionTestBase): + """Compliance: a probe whose ``when`` condition cannot be parsed must be rejected with an ERROR diagnostic.""" + + def setup_invalid_condition_dsl_probe_rejected(self) -> None: + self.initialize_weblog_remote_config() + + probes = debugger.read_probes("probe_invalid_condition_dsl") + for probe in probes: + probe["id"] = debugger.generate_probe_id("log") + self._convert_to_line_probe_if_needed(probe) + + self.set_probes(probes) + self.send_rc_probes() + # Wait until each probe reaches a terminal state. ERROR is the conforming + # target (the tracer rejected the broken DSL); INSTALLED catches non-conforming + # tracers that wrongly install the probe -- returning on INSTALLED keeps those + # bug paths fast. The 15s timeout fires only for tracers that emit no diagnostic + # at all; those fail the diagnostic-presence assertion below. + self.wait_for_all_probes(statuses=["INSTALLED", "ERROR"], timeout=15) + # Trigger the instrumented method so any lazy-compilation paths have a chance to + # surface the failure. A conforming tracer must not emit a snapshot here. + self.send_weblog_request("/debugger/log") + # Drains any (non-conforming) snapshot the tracer may have emitted so the + # snapshot assertion below sees it. Returns immediately if none arrives. + self.wait_for_all_snapshots(timeout=5) + + def test_invalid_condition_dsl_probe_rejected(self) -> None: + """A probe whose ``when`` condition cannot be parsed must be rejected by the tracer. + + Concretely: the tracer must emit a status=ERROR diagnostic (so the developer who + configured the broken probe knows it was rejected) and must not produce any probe + result (snapshot). + """ + self.collect() + self.assert_setup_ok() + + # ``self.probe_diagnostics[probe_id]["status"]`` is the framework-aggregated + # "most-advanced" status; ERROR is only stored if it's reached from RECEIVED. + # An INSTALLED -> ERROR transition would be hidden (test would see INSTALLED). + for probe_id in self.probe_ids: + diag = self.probe_diagnostics.get(probe_id) + assert diag is not None, ( + "The probe did not receive any diagnostic; the tracer must emit a " + "status=ERROR diagnostic so the developer knows the probe was rejected." + ) + status = diag["status"] + assert status == "ERROR", ( + f"Probe reached status {status!r}; a probe with invalid DSL must reach " + f"status=ERROR so the developer knows it was rejected." + ) + snapshots = self.probe_snapshots.get(probe_id, []) + assert not snapshots, ( + f"Probe emitted {len(snapshots)} snapshot(s); a probe with invalid DSL " + f"must not produce any probe result." + ) + + +@features.debugger_expression_language +@scenarios.debugger_probes_snapshot +class Test_Debugger_Runtime_Condition_Error(_ConditionTestBase): + """Compliance: a probe whose ``when`` raises at eval time must surface the error without leaking probe data.""" + + def _setup_runtime_condition_error(self, request_count: int = 1, spacing_s: float = 0.0) -> None: + self.initialize_weblog_remote_config() + + probes = debugger.read_probes("probe_runtime_condition_error") + for probe in probes: + probe["id"] = debugger.generate_probe_id("log") + self._convert_to_line_probe_if_needed(probe) + + self.set_probes(probes) + self.send_rc_probes() + if not self.wait_for_all_probes(statuses=["INSTALLED"], timeout=30): + self.setup_failures.append("Probes did not reach INSTALLED status within 30s") + + for i in range(request_count): + self.send_weblog_request("/debugger/log", reset=(i == 0)) + if spacing_s > 0 and i < request_count - 1: + time.sleep(spacing_s) + + if spacing_s > 0: + # In the multi-hit case we deliberately do NOT use wait_for_all_snapshots: + # a non-conforming tracer emits one eval-error snapshot per hit (so two + # in total here). wait_for_all_snapshots returns the moment the first + # snapshot is on disk, which would leave the second snapshot still in + # flight when collect() reads disk -- a false pass. A fixed sleep gives + # the second snapshot enough time to land. + time.sleep(5) + else: + # Returns as soon as the snapshot arrives, or after the timeout for tracers + # that emit nothing. + self.wait_for_all_snapshots(timeout=5) + + def setup_runtime_condition_error_probe_installs(self) -> None: + self._setup_runtime_condition_error() + + def test_runtime_condition_error_probe_installs(self) -> None: + """A probe whose ``when`` is structurally valid must install successfully.""" + self.collect() + self.assert_setup_ok() + self.assert_rc_state_not_error() + + for probe_id in self.probe_ids: + assert probe_id in self.probe_diagnostics, "Expected a diagnostic for the probe, but none was received." + status = self.probe_diagnostics[probe_id]["status"] + assert status in ("INSTALLED", "EMITTING"), ( + f"Expected the probe to reach INSTALLED/EMITTING status, got {status!r}." + ) + + def setup_runtime_condition_error_emits_error_only_snapshot(self) -> None: + self._setup_runtime_condition_error() + + def test_runtime_condition_error_emits_error_only_snapshot(self) -> None: + """When the ``when`` raises at eval time, the tracer must emit a probe result that: + + * carries a non-empty ``evaluationErrors[]`` array (so the developer is informed + that their condition is broken), AND + * does NOT include captured probe data (the condition was not successfully + evaluated, so the probe did not effectively fire from the user-data perspective). + """ + self.collect() + self.assert_setup_ok() + + for probe_id in self.probe_ids: + snapshots = self.probe_snapshots.get(probe_id, []) + assert snapshots, ( + "The probe emitted no snapshot at all; a probe whose condition errors " + "at runtime must surface the error to the user via a probe result with " + "evaluationErrors[]." + ) + + envelope = snapshots[0] + snap = envelope.get("debugger", {}).get("snapshot") or envelope.get("debugger.snapshot") or {} + + evaluation_errors = snap.get("evaluationErrors") or [] + assert evaluation_errors, ( + "The probe emitted a snapshot without a populated evaluationErrors[] " + "array; the developer must be told what failed." + ) + + # The condition in probe_runtime_condition_error.json references the unbound + # variable `definitelyDoesNotExist`. At least one evaluation-error entry should + # mention that variable name (in `expr` or `message`) so the developer can pin + # down which part of the condition failed. + mentions_var = any( + "definitelyDoesNotExist" in str(entry.get("expr", "")) + or "definitelyDoesNotExist" in str(entry.get("message", "")) + for entry in evaluation_errors + ) + assert mentions_var, ( + f"The probe emitted an eval-error snapshot whose evaluationErrors[] does " + f"not mention the failing variable name 'definitelyDoesNotExist' " + f"(entries: {evaluation_errors!r}); the developer must be able to pin " + f"down which part of the condition failed." + ) + + captures = snap.get("captures") + assert not _captures_contain_data(captures), ( + f"The probe emitted a snapshot whose ``captures`` field contains captured " + f"data ({captures!r}); an eval-error snapshot must have empty captures " + f"because the condition was not successfully evaluated." + ) + + def setup_runtime_condition_error_rate_limited(self) -> None: + # Fire two hits >1s apart so each hit clears any general 1/s snapshot sampler; + # anything dropping the second snapshot must be the eval-error rate limiter. + self._setup_runtime_condition_error(request_count=2, spacing_s=1.5) + + def test_runtime_condition_error_rate_limited(self) -> None: + """At most 1 eval-error snapshot per probe per 5 minutes.""" + self.collect() + self.assert_setup_ok() + + for probe_id in self.probe_ids: + snapshots = self.probe_snapshots.get(probe_id, []) + assert len(snapshots) <= 1, ( + f"The probe emitted {len(snapshots)} eval-error snapshots for two hits " + f"spaced >1s apart. A conforming tracer must rate-limit to at most 1 " + f"eval-error snapshot per probe per 5 minutes." + ) diff --git a/tests/debugger/utils.py b/tests/debugger/utils.py index cd59c84a209..88d6386352e 100644 --- a/tests/debugger/utils.py +++ b/tests/debugger/utils.py @@ -159,6 +159,7 @@ def method_and_language_to_line_number(self, method: str, language: str) -> list """method_and_language_to_line_number returns the respective line number given the method and language""" definitions: dict[str, dict[str, list[int]]] = { "Budgets": {"java": [138], "dotnet": [136], "python": [142]}, + "LogProbe": {"nodejs": [20]}, "Expression": {"java": [71], "dotnet": [74], "python": [72], "ruby": [82], "nodejs": [82], "golang": [71]}, # The `@exception` variable is not available in the context of line probes. "ExpressionException": {}, diff --git a/tests/debugger/utils/probes/probe_invalid_condition_dsl.json b/tests/debugger/utils/probes/probe_invalid_condition_dsl.json new file mode 100644 index 00000000000..4ac4d51fe93 --- /dev/null +++ b/tests/debugger/utils/probes/probe_invalid_condition_dsl.json @@ -0,0 +1,25 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "LogProbe", + "sourceFile": null + }, + "when": { + "dsl": "notARealOperator(foo)", + "json": { + "notARealOperator": [ + {"ref": "foo"} + ] + } + }, + "captureSnapshot": true, + "segments": [ + {"str": "invalid-condition-dsl probe fired (should never happen)"} + ] + } +] diff --git a/tests/debugger/utils/probes/probe_runtime_condition_error.json b/tests/debugger/utils/probes/probe_runtime_condition_error.json new file mode 100644 index 00000000000..0e91e457e46 --- /dev/null +++ b/tests/debugger/utils/probes/probe_runtime_condition_error.json @@ -0,0 +1,26 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "LogProbe", + "sourceFile": null + }, + "when": { + "dsl": "definitelyDoesNotExist == \"never\"", + "json": { + "eq": [ + {"ref": "definitelyDoesNotExist"}, + "never" + ] + } + }, + "captureSnapshot": true, + "segments": [ + {"str": "runtime-condition-error probe fired"} + ] + } +] diff --git a/tests/schemas/test_schemas.py b/tests/schemas/test_schemas.py index 3afaf02b593..73a7102e06b 100644 --- a/tests/schemas/test_schemas.py +++ b/tests/schemas/test_schemas.py @@ -56,6 +56,12 @@ def test_library(self): and context.scenario is scenarios.debugger_expression_language, ticket="APMRP-360", ), + SchemaBug( + endpoint="/debugger/v2/input", + data_path="$[].debugger.snapshot.stack", + condition=context.library == "python" and context.scenario is scenarios.debugger_probes_snapshot, + ticket="DEBUG-0000", + ), SchemaBug( endpoint="/debugger/v1/input", data_path="$[].debugger.snapshot.probe.location.method", @@ -158,6 +164,12 @@ def test_agent(self): condition=context.library == "dotnet" and context.scenario is scenarios.debugger_symdb, ticket="DEBUG-3298", ), + SchemaBug( + endpoint="/api/v2/debugger", + data_path="$[]", + condition=context.library == "python" and context.scenario is scenarios.debugger_probes_snapshot, + ticket="DEBUG-0000", + ), SchemaBug( endpoint="/api/v2/apmtelemetry", data_path="$.payload", From 8dba0d29277a3e6f3099abb393779bcc8348e470 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 27 May 2026 12:07:43 +0200 Subject: [PATCH 2/2] Add missing JIRA ticket numbers --- tests/schemas/test_schemas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/schemas/test_schemas.py b/tests/schemas/test_schemas.py index 73a7102e06b..b9645ad55fa 100644 --- a/tests/schemas/test_schemas.py +++ b/tests/schemas/test_schemas.py @@ -60,7 +60,7 @@ def test_library(self): endpoint="/debugger/v2/input", data_path="$[].debugger.snapshot.stack", condition=context.library == "python" and context.scenario is scenarios.debugger_probes_snapshot, - ticket="DEBUG-0000", + ticket="DEBUG-5715", ), SchemaBug( endpoint="/debugger/v1/input", @@ -168,7 +168,7 @@ def test_agent(self): endpoint="/api/v2/debugger", data_path="$[]", condition=context.library == "python" and context.scenario is scenarios.debugger_probes_snapshot, - ticket="DEBUG-0000", + ticket="DEBUG-5715", ), SchemaBug( endpoint="/api/v2/apmtelemetry",