From d4589f517880f16c6d51a71acb561c13ec6f48bd Mon Sep 17 00:00:00 2001 From: Zach Montoya Date: Mon, 9 Sep 2024 16:49:23 -0700 Subject: [PATCH 1/2] Add tests for DD_TRACE_HTTP_CLIENT_ERROR_STATUSES config flag --- manifests/cpp.yml | 2 + manifests/dotnet.yml | 2 + manifests/golang.yml | 2 + manifests/java.yml | 2 + manifests/nodejs.yml | 2 + manifests/php.yml | 2 + manifests/python.yml | 2 + manifests/ruby.yml | 2 + tests/test_config_consistency.py | 84 +++++++++++++++++++++++++++ utils/_context/_scenarios/__init__.py | 7 ++- 10 files changed, 106 insertions(+), 1 deletion(-) diff --git a/manifests/cpp.yml b/manifests/cpp.yml index 5bfd049fb28..c8c53b8439b 100644 --- a/manifests/cpp.yml +++ b/manifests/cpp.yml @@ -162,6 +162,8 @@ tests/: test_miscs.py: Test_Miscs: missing_feature test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_distributed.py: diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 27c0e6fb02a..162a6239559 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -350,6 +350,8 @@ tests/: Test_RemoteConfigurationUpdateSequenceLiveDebugging: v2.15.0 Test_RemoteConfigurationUpdateSequenceLiveDebuggingNoCache: irrelevant (cache is implemented) test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_data_integrity.py: diff --git a/manifests/golang.yml b/manifests/golang.yml index 5322cd7eced..77a56b3040c 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -472,6 +472,8 @@ tests/: Test_RemoteConfigurationUpdateSequenceLiveDebugging: missing_feature Test_RemoteConfigurationUpdateSequenceLiveDebuggingNoCache: irrelevant (cache is implemented) test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_data_integrity.py: diff --git a/manifests/java.yml b/manifests/java.yml index 51eae9c1f74..3de824cae7c 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -1219,6 +1219,8 @@ tests/: Test_Mock: v0.0.99 Test_NotReleased: missing_feature test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_data_integrity.py: diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 610f6e8db6c..7789108ea16 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -523,6 +523,8 @@ tests/: Test_RemoteConfigurationUpdateSequenceLiveDebugging: *ref_5_16_0 #actual version unknown Test_RemoteConfigurationUpdateSequenceLiveDebuggingNoCache: irrelevant (cache is implemented) test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_distributed.py: diff --git a/manifests/php.yml b/manifests/php.yml index 409594f3852..5f318e3475a 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -309,6 +309,8 @@ tests/: test_miscs.py: Test_Miscs: missing_feature test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_distributed.py: diff --git a/manifests/python.yml b/manifests/python.yml index af777ac7a12..af8099d35dd 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -729,6 +729,8 @@ tests/: Test_RemoteConfigurationUpdateSequenceLiveDebugging: v2.8.0.dev Test_RemoteConfigurationUpdateSequenceLiveDebuggingNoCache: missing_feature test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_data_integrity.py: diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 4638bf09b66..e878f8976c5 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -372,6 +372,8 @@ tests/: test_miscs.py: Test_Miscs: missing_feature test_config_consistency.py: + Test_Config_HttpClientErrorStatuses_Default: missing_feature + Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: missing_feature Test_Config_HttpServerErrorStatuses_Default: missing_feature Test_Config_HttpServerErrorStatuses_FeatureFlagCustom: missing_feature test_distributed.py: diff --git a/tests/test_config_consistency.py b/tests/test_config_consistency.py index 3f41d134719..1d34abc3793 100644 --- a/tests/test_config_consistency.py +++ b/tests/test_config_consistency.py @@ -2,6 +2,7 @@ # This product includes software developed at Datadog (https://www.datadoghq.com/). # Copyright 2022 Datadog, Inc. +import json from utils import weblog, interfaces, scenarios, features @@ -71,3 +72,86 @@ def test_status_code_202(self): assert spans[0]["type"] == "web" assert spans[0]["meta"]["http.status_code"] == "202" assert spans[0]["error"] == 1 + + +@scenarios.default +@features.tracing_configuration_consistency +class Test_Config_HttpClientErrorStatuses_Default: + """ Verify behavior of http clients """ + + def setup_status_code_400(self): + self.r = weblog.get("/make_distant_call", params={"url": "http://weblog:7777/status?code=400"}) + + def test_status_code_400(self): + assert self.r.status_code == 200 + content = json.loads(self.r.text) + assert content["status_code"] == 400 + + interfaces.library.assert_trace_exists(self.r) + spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] + + client_span = _get_span_by_name(spans, "http.request") + + assert client_span.get("meta").get("http.status_code") == "400" + assert client_span.get("error") == 1 + + def setup_status_code_500(self): + self.r = weblog.get("/make_distant_call", params={"url": "http://weblog:7777/status?code=500"}) + + def test_status_code_500(self): + assert self.r.status_code == 200 + content = json.loads(self.r.text) + assert content["status_code"] == 500 + + interfaces.library.assert_trace_exists(self.r) + spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] + + client_span = _get_span_by_name(spans, "http.request") + + assert client_span.get("meta").get("http.status_code") == "500" + assert client_span.get("error") == None or client_span.get("error") == 0 + + +@scenarios.tracing_config_nondefault +@features.tracing_configuration_consistency +class Test_Config_HttpClientErrorStatuses_FeatureFlagCustom: + """ Verify behavior of http clients """ + + def setup_status_code_200(self): + self.r = weblog.get("/make_distant_call", params={"url": "http://weblog:7777/status?code=200"}) + + def test_status_code_200(self): + assert self.r.status_code == 200 + content = json.loads(self.r.text) + assert content["status_code"] == 200 + + interfaces.library.assert_trace_exists(self.r) + spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] + + client_span = _get_span_by_name(spans, "http.request") + + assert client_span.get("meta").get("http.status_code") == "200" + assert client_span.get("error") == 1 + + def setup_status_code_202(self): + self.r = weblog.get("/make_distant_call", params={"url": "http://weblog:7777/status?code=202"}) + + def test_status_code_202(self): + assert self.r.status_code == 200 + content = json.loads(self.r.text) + assert content["status_code"] == 202 + + interfaces.library.assert_trace_exists(self.r) + spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] + + client_span = _get_span_by_name(spans, "http.request") + + assert client_span.get("meta").get("http.status_code") == "202" + assert client_span.get("error") == 1 + + +def _get_span_by_name(spans, span_name): + for s in spans: + if s["name"] == span_name: + return s + return {} diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index d6637ed9d5b..9cbd305a2a6 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -471,7 +471,12 @@ def all_endtoend_scenarios(test_object): ) tracing_config_nondefault = EndToEndScenario( - "TRACING_CONFIG_NONDEFAULT", weblog_env={"DD_TRACE_HTTP_SERVER_ERROR_STATUSES": "200-201,202"}, doc="", + "TRACING_CONFIG_NONDEFAULT", + weblog_env={ + "DD_TRACE_HTTP_SERVER_ERROR_STATUSES": "200-201,202", + "DD_TRACE_HTTP_CLIENT_ERROR_STATUSES": "200-201,202", + }, + doc="", ) parametric = ParametricScenario("PARAMETRIC", doc="WIP") From 6fb43882ec7a7490c3d1d75fac41258f51d33dab Mon Sep 17 00:00:00 2001 From: Zach Montoya Date: Mon, 16 Sep 2024 12:14:17 -0700 Subject: [PATCH 2/2] Update span search for a more deterministic resource name + tag lookup --- tests/test_config_consistency.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_config_consistency.py b/tests/test_config_consistency.py index 1d34abc3793..648a5da1bf4 100644 --- a/tests/test_config_consistency.py +++ b/tests/test_config_consistency.py @@ -90,7 +90,7 @@ def test_status_code_400(self): interfaces.library.assert_trace_exists(self.r) spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] - client_span = _get_span_by_name(spans, "http.request") + client_span = _get_span(spans, resource_name="GET /status", tags={"span.kind": "client"}) assert client_span.get("meta").get("http.status_code") == "400" assert client_span.get("error") == 1 @@ -106,7 +106,7 @@ def test_status_code_500(self): interfaces.library.assert_trace_exists(self.r) spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] - client_span = _get_span_by_name(spans, "http.request") + client_span = _get_span(spans, resource_name="GET /status", tags={"span.kind": "client"}) assert client_span.get("meta").get("http.status_code") == "500" assert client_span.get("error") == None or client_span.get("error") == 0 @@ -128,7 +128,7 @@ def test_status_code_200(self): interfaces.library.assert_trace_exists(self.r) spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] - client_span = _get_span_by_name(spans, "http.request") + client_span = _get_span(spans, resource_name="GET /status", tags={"span.kind": "client"}) assert client_span.get("meta").get("http.status_code") == "200" assert client_span.get("error") == 1 @@ -144,14 +144,25 @@ def test_status_code_202(self): interfaces.library.assert_trace_exists(self.r) spans = [s for _, _, s in interfaces.library.get_spans(request=self.r, full_trace=True)] - client_span = _get_span_by_name(spans, "http.request") + client_span = _get_span(spans, resource_name="GET /status", tags={"span.kind": "client"}) assert client_span.get("meta").get("http.status_code") == "202" assert client_span.get("error") == 1 -def _get_span_by_name(spans, span_name): +def _get_span(spans, resource_name, tags): for s in spans: - if s["name"] == span_name: + match = True + if s["resource"] != resource_name: + continue + + for tagKey in tags: + if tagKey in s["meta"]: + expectValue = tags[tagKey] + actualValue = s["meta"][tagKey] + if expectValue != actualValue: + continue + + if match: return s return {}