diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 6133abc9d72..dfc03f93b3a 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -657,6 +657,11 @@ manifest: '*': missing_feature (Not yet implemented) poc: v3.44.0 uds: v3.44.0 + ? tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_complex_capture_expressions_dictionary_value + : - weblog_declaration: + '*': missing_feature (Not yet implemented) + poc: v3.44.0 + uds: v3.44.0 tests/debugger/test_debugger_capture_expressions_errors.py::Test_Debugger_Filter_Rejected_Capture_Expression_Error: # ProbeProcessor skips captureExpressions when `when` is false; #8725 rate-limits eval-error snapshots to 1/5min - weblog_declaration: '*': missing_feature (Not yet implemented) diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 502a9a722ee..4114b7a5371 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -1898,6 +1898,8 @@ manifest: rails80: v2.38.0 uds-rails: v2.38.0 tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_complex_capture_expressions: missing_feature (Local variable capture not implemented for method probes) + ? tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_complex_capture_expressions_dictionary_value + : missing_feature (Local variable capture not implemented for method probes) ? tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_method_local_capture_expressions : missing_feature (Local variable capture not implemented for method probes) ? tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_method_parameter_capture_expressions diff --git a/tests/debugger/test_debugger_capture_expressions.py b/tests/debugger/test_debugger_capture_expressions.py index 0898a53aaa2..51bcc93ffb3 100644 --- a/tests/debugger/test_debugger_capture_expressions.py +++ b/tests/debugger/test_debugger_capture_expressions.py @@ -202,6 +202,24 @@ def test_complex_capture_expressions(self): self._validate_capture_expressions(expected_captures) + def setup_complex_capture_expressions_dictionary_value(self): + self._setup("probe_capture_expressions_complex", "/debugger/expression?inputValue=testValue", "log", lines=None) + + def test_complex_capture_expressions_dictionary_value(self): + """``testStruct.Dictionary['two']`` names an existing key, so it must capture 2, not a not-captured marker.""" + self._assert() + + expected_captures = { + probe_id: { + "dictionary_value": lambda v: ( + isinstance(v, dict) and "notCapturedReason" not in v and str(v.get("value")) == "2" + ), + } + for probe_id in self.probe_ids + } + + self._validate_capture_expressions(expected_captures) + @features.debugger_line_probe @scenarios.debugger_probes_snapshot diff --git a/tests/debugger/test_debugger_go_expression_errors.py b/tests/debugger/test_debugger_go_expression_errors.py new file mode 100644 index 00000000000..b3fc18a6fe5 --- /dev/null +++ b/tests/debugger/test_debugger_go_expression_errors.py @@ -0,0 +1,72 @@ +# 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. + +"""Go expression errors that the system-probe can detect, because Go types are fixed at compile time.""" + +import tests.debugger.utils as debugger +from utils import context, features, irrelevant, scenarios + + +class _GoExpressionTestBase(debugger.BaseDebuggerTest): + def _setup_probes(self, probes_name: str, install_statuses: list[debugger.ProbeStatus]) -> None: + self.initialize_weblog_remote_config() + + probes = debugger.read_probes(probes_name) + for probe in probes: + probe["id"] = debugger.generate_probe_id("log") + + self.set_probes(probes) + self.send_rc_probes() + self.wait_for_all_probes(statuses=install_statuses, timeout=30) + self.send_weblog_request("/debugger/expression?inputValue=asd") + self.wait_for_all_snapshots(timeout=10) + + +@features.debugger_expression_language +@scenarios.debugger_probes_snapshot +@irrelevant(context.library != "golang", reason="Only Go checks expression types at install time") +class Test_Debugger_Go_Static_Template_Error(_GoExpressionTestBase): + """A template the system-probe already knows can never evaluate must be rejected, like the same condition is.""" + + def setup_static_template_error_probe_rejected(self) -> None: + self._setup_probes("probe_static_template_error", ["INSTALLED", "ERROR"]) + + def test_static_template_error_probe_rejected(self) -> None: + """Reject log templates with an invalid map member, unknown variable, or missing struct field.""" + self.collect() + self.assert_setup_ok() + + 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." + assert diag["status"] == "ERROR", ( + f"Probe reached status {diag['status']!r}; its template can never evaluate, so it must be " + f"rejected with status=ERROR like the equivalent condition." + ) + + +@features.debugger_expression_language +@scenarios.debugger_probes_snapshot +@irrelevant(context.library != "golang", reason="Go-specific repro for map index conditions") +class Test_Debugger_Go_Missing_Map_Key_Condition(_GoExpressionTestBase): + """A condition on a key that isn't in the map must not fire.""" + + def setup_missing_map_key_condition_does_not_fire(self) -> None: + self._setup_probes("probe_missing_map_key_condition", ["INSTALLED"]) + + def test_missing_map_key_condition_does_not_fire(self) -> None: + """A missing Go map key reads as 0, so this condition must not capture data.""" + self.collect() + self.assert_setup_ok() + + for probe_id in self.probe_ids: + diag = self.probe_diagnostics.get(probe_id) + assert diag is not None, f"Probe {probe_id} received no install diagnostic" + assert diag["status"] in ("INSTALLED", "EMITTING"), f"Probe {probe_id} did not install: {diag['status']!r}" + for envelope in self.probe_snapshots.get(probe_id, []): + snap = envelope.get("debugger", {}).get("snapshot") or envelope.get("debugger.snapshot") or {} + assert not debugger.captures_contain_data(snap.get("captures")), ( + "The probe emitted a snapshot with captured data, so the condition fired as if true. " + "A missing key in a map[string]int reads as 0, and 0 == 1 is false." + ) diff --git a/tests/debugger/utils/probes/probe_missing_map_key_condition.json b/tests/debugger/utils/probes/probe_missing_map_key_condition.json new file mode 100644 index 00000000000..7a082dcdbba --- /dev/null +++ b/tests/debugger/utils/probes/probe_missing_map_key_condition.json @@ -0,0 +1,31 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "Expression", + "sourceFile": null + }, + "when": { + "dsl": "testStruct.Dictionary['nope'] == 1", + "json": { + "eq": [ + { + "index": [ + {"getmember": [{"ref": "testStruct"}, "Dictionary"]}, + "nope" + ] + }, + 1 + ] + } + }, + "captureSnapshot": true, + "segments": [ + {"str": "missing-map-key condition fired"} + ] + } +] diff --git a/tests/debugger/utils/probes/probe_static_template_error.json b/tests/debugger/utils/probes/probe_static_template_error.json new file mode 100644 index 00000000000..9f1a3076892 --- /dev/null +++ b/tests/debugger/utils/probes/probe_static_template_error.json @@ -0,0 +1,86 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "Expression", + "sourceFile": null + }, + "captureSnapshot": false, + "segments": [ + { + "str": "dot on map: " + }, + { + "dsl": "testStruct.Dictionary.two", + "json": { + "getmember": [ + { + "getmember": [ + { + "ref": "testStruct" + }, + "Dictionary" + ] + }, + "two" + ] + } + } + ] + }, + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "Expression", + "sourceFile": null + }, + "captureSnapshot": false, + "segments": [ + { + "str": "unknown variable: " + }, + { + "dsl": "definitelyDoesNotExist", + "json": { + "ref": "definitelyDoesNotExist" + } + } + ] + }, + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "Expression", + "sourceFile": null + }, + "captureSnapshot": false, + "segments": [ + { + "str": "missing struct field: " + }, + { + "dsl": "testStruct.DefinitelyDoesNotExist", + "json": { + "getmember": [ + { + "ref": "testStruct" + }, + "DefinitelyDoesNotExist" + ] + } + } + ] + } +] diff --git a/utils/build/docker/golang/app/chi/debugger.go b/utils/build/docker/golang/app/chi/debugger.go index f02170c58cc..c4d0d409a3a 100644 --- a/utils/build/docker/golang/app/chi/debugger.go +++ b/utils/build/docker/golang/app/chi/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/echo/debugger.go b/utils/build/docker/golang/app/echo/debugger.go index f02170c58cc..c4d0d409a3a 100644 --- a/utils/build/docker/golang/app/echo/debugger.go +++ b/utils/build/docker/golang/app/echo/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/fiber-v2-orchestrion/debugger.go b/utils/build/docker/golang/app/fiber-v2-orchestrion/debugger.go index 37a36a8fb80..8d31c56a2ef 100644 --- a/utils/build/docker/golang/app/fiber-v2-orchestrion/debugger.go +++ b/utils/build/docker/golang/app/fiber-v2-orchestrion/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/gin/debugger.go b/utils/build/docker/golang/app/gin/debugger.go index f02170c58cc..c4d0d409a3a 100644 --- a/utils/build/docker/golang/app/gin/debugger.go +++ b/utils/build/docker/golang/app/gin/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/net-http-orchestrion/debugger.go b/utils/build/docker/golang/app/net-http-orchestrion/debugger.go index f02170c58cc..c4d0d409a3a 100644 --- a/utils/build/docker/golang/app/net-http-orchestrion/debugger.go +++ b/utils/build/docker/golang/app/net-http-orchestrion/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/net-http-span-pool/debugger.go b/utils/build/docker/golang/app/net-http-span-pool/debugger.go index 37a36a8fb80..8d31c56a2ef 100644 --- a/utils/build/docker/golang/app/net-http-span-pool/debugger.go +++ b/utils/build/docker/golang/app/net-http-span-pool/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } } diff --git a/utils/build/docker/golang/app/net-http/debugger.go b/utils/build/docker/golang/app/net-http/debugger.go index f02170c58cc..c4d0d409a3a 100644 --- a/utils/build/docker/golang/app/net-http/debugger.go +++ b/utils/build/docker/golang/app/net-http/debugger.go @@ -88,7 +88,7 @@ func newExpressionTestStruct() ExpressionTestStruct { StringValue: "one", BoolValue: true, Collection: []string{"one", "two", "three"}, - Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}, + Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}, } }