Skip to content

Commit aba901b

Browse files
authored
test(parametric): decouple nodejs stable-config assertions from renamable tracer internals (#7196)
1 parent 9f990b1 commit aba901b

7 files changed

Lines changed: 275 additions & 85 deletions

File tree

manifests/nodejs.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2026,7 +2026,6 @@ manifest:
20262026
- declaration: missing_feature (Implemented in 5.38.0)
20272027
component_version: <5.38.0
20282028
tests/parametric/test_config_consistency.py::Test_Config_Dogstatsd: *ref_5_29_0
2029-
tests/parametric/test_config_consistency.py::Test_Config_Dogstatsd::test_dogstatsd_default: incomplete_test_app (PHP parameteric app can not access the dogstatsd default values, this logic is internal to the tracer)
20302029
tests/parametric/test_config_consistency.py::Test_Config_RateLimit: *ref_5_25_0
20312030
tests/parametric/test_config_consistency.py::Test_Config_Tags: *ref_5_41_0
20322031
tests/parametric/test_config_consistency.py::Test_Config_TraceAgentURL: *ref_5_25_0

tests/parametric/conftest.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import base64
22
from collections.abc import Generator
3+
import json
34
from pathlib import Path
45
import shutil
56
import subprocess
@@ -131,3 +132,60 @@ def write_stable_config_content(self, stable_config_content: str, path: str, tes
131132
cmd = "sudo " + cmd
132133
success, message = test_library.container_exec_run(cmd)
133134
assert success, message
135+
136+
137+
# dd_* keys whose canonical telemetry name is not simply the upper-cased key. The tracer
138+
# reports the canonical name (e.g. DD_TRACE_LOG_LEVEL); DD_LOG_LEVEL is only an alias.
139+
_TELEMETRY_NAME_OVERRIDES = {"dd_log_level": "DD_TRACE_LOG_LEVEL"}
140+
141+
142+
def _telemetry_name(dd_key: str) -> str:
143+
return _TELEMETRY_NAME_OVERRIDES.get(dd_key, dd_key.upper())
144+
145+
146+
def nodejs_telemetry_value(test_agent: TestAgentAPI, dd_key: str) -> str | int | float | bool | None:
147+
"""Return the effective nodejs telemetry value for a dd_* config key.
148+
149+
dd-trace-js builds /trace/config from internal property paths that a series of config
150+
PRs is renaming to canonical names; the telemetry keeps reporting the stable canonical
151+
names, so asserting there stays decoupled from those refactors. The effective value is
152+
the highest-seq_id entry (already sorted first).
153+
"""
154+
name = _telemetry_name(dd_key)
155+
configuration_by_name = test_agent.wait_for_telemetry_configurations()
156+
entries = configuration_by_name.get(name)
157+
assert entries, f"No telemetry configuration '{name}'"
158+
return entries[0].get("value")
159+
160+
161+
def assert_nodejs_telemetry_config(test_agent: TestAgentAPI, expected: dict) -> None:
162+
"""Assert expected dd_* config values against the nodejs telemetry configuration."""
163+
configuration_by_name = test_agent.wait_for_telemetry_configurations()
164+
for dd_key, expected_value in expected.items():
165+
name = _telemetry_name(dd_key)
166+
entries = configuration_by_name.get(name)
167+
assert entries, f"No telemetry configuration '{name}'"
168+
actual = entries[0].get("value")
169+
if dd_key == "dd_tags":
170+
actual_tags = "" if actual is None else str(actual)
171+
expected_tags = expected_value if isinstance(expected_value, list) else str(expected_value).split(",")
172+
for tag in expected_tags:
173+
assert tag in actual_tags, f"Expected tag '{tag}' not found in telemetry tags: {actual_tags}"
174+
else:
175+
assert str(actual).lower() == str(expected_value).lower(), f"Expected {name}={expected_value}, got {actual}"
176+
177+
178+
def nodejs_startup_config(test_library: APMLibrary) -> dict:
179+
"""Parse the tracer's published `DATADOG TRACER CONFIGURATION - {json}` startup line.
180+
181+
Some facts have no telemetry signal: an unreachable agent never delivers telemetry (so the
182+
resolved agent URL is unobservable that way), and OTEL_LOG_LEVEL=debug toggles debug logging
183+
without a DD_TRACE_DEBUG telemetry entry. The startup diagnostic is a published, user-facing
184+
log line that carries them, so it is the non-internal source for those cases.
185+
"""
186+
marker = "DATADOG TRACER CONFIGURATION - "
187+
for line in test_library.get_logs().splitlines():
188+
index = line.find(marker)
189+
if index != -1:
190+
return json.loads(line[index + len(marker) :])
191+
raise AssertionError("No 'DATADOG TRACER CONFIGURATION' line found in container logs")

tests/parametric/test_config_consistency.py

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
from utils.docker_fixtures.spec.trace import find_span_in_traces, find_only_span
1515
from utils.docker_fixtures import TestAgentAPI
16-
from .conftest import APMLibrary, StableConfigWriter
16+
from .conftest import APMLibrary, StableConfigWriter, assert_nodejs_telemetry_config, nodejs_startup_config
1717

1818
parametrize = pytest.mark.parametrize
1919

@@ -136,6 +136,7 @@ class Test_Config_TraceAgentURL:
136136
"library_env",
137137
[
138138
{
139+
"DD_TRACE_STARTUP_LOGS": "true",
139140
"DD_TRACE_AGENT_URL": "unix:///var/run/datadog/apm.socket",
140141
"DD_AGENT_HOST": "localhost",
141142
"DD_TRACE_AGENT_PORT": "8126",
@@ -144,9 +145,9 @@ class Test_Config_TraceAgentURL:
144145
)
145146
def test_dd_trace_agent_unix_url_nonexistent(self, test_library: APMLibrary):
146147
with test_library as t:
147-
resp = t.config()
148+
agent_url = _trace_agent_url(t)
148149

149-
url = urlparse(resp["dd_trace_agent_url"])
150+
url = urlparse(agent_url)
150151
assert "unix" in url.scheme
151152
assert url.path == "/var/run/datadog/apm.socket"
152153

@@ -155,6 +156,7 @@ def test_dd_trace_agent_unix_url_nonexistent(self, test_library: APMLibrary):
155156
"library_env",
156157
[
157158
{
159+
"DD_TRACE_STARTUP_LOGS": "true",
158160
"DD_TRACE_AGENT_URL": "http://random-host:9999/",
159161
"DD_AGENT_HOST": "localhost",
160162
"DD_TRACE_AGENT_PORT": "8126",
@@ -163,9 +165,9 @@ def test_dd_trace_agent_unix_url_nonexistent(self, test_library: APMLibrary):
163165
)
164166
def test_dd_trace_agent_http_url_nonexistent(self, test_library: APMLibrary):
165167
with test_library as t:
166-
resp = t.config()
168+
agent_url = _trace_agent_url(t)
167169

168-
url = urlparse(resp["dd_trace_agent_url"])
170+
url = urlparse(agent_url)
169171
assert url.scheme == "http"
170172
assert url.hostname == "random-host"
171173
assert url.port == 9999
@@ -174,6 +176,7 @@ def test_dd_trace_agent_http_url_nonexistent(self, test_library: APMLibrary):
174176
"library_env",
175177
[
176178
{
179+
"DD_TRACE_STARTUP_LOGS": "true",
177180
"DD_TRACE_AGENT_URL": "http://[::1]:5000",
178181
"DD_AGENT_HOST": "localhost",
179182
"DD_TRACE_AGENT_PORT": "8126",
@@ -182,9 +185,9 @@ def test_dd_trace_agent_http_url_nonexistent(self, test_library: APMLibrary):
182185
)
183186
def test_dd_trace_agent_http_url_ipv6(self, test_library: APMLibrary):
184187
with test_library as t:
185-
resp = t.config()
188+
agent_url = _trace_agent_url(t)
186189

187-
url = urlparse(resp["dd_trace_agent_url"])
190+
url = urlparse(agent_url)
188191
assert url.scheme == "http"
189192
assert url.hostname == "::1"
190193
assert url.port == 5000
@@ -193,6 +196,7 @@ def test_dd_trace_agent_http_url_ipv6(self, test_library: APMLibrary):
193196
"library_env",
194197
[
195198
{
199+
"DD_TRACE_STARTUP_LOGS": "true",
196200
"DD_TRACE_AGENT_URL": "", # Empty string passed to make sure conftest.py does not set trace agent url
197201
"DD_AGENT_HOST": "[::1]",
198202
"DD_TRACE_AGENT_PORT": "5000",
@@ -201,9 +205,9 @@ def test_dd_trace_agent_http_url_ipv6(self, test_library: APMLibrary):
201205
)
202206
def test_dd_agent_host_ipv6(self, test_library: APMLibrary):
203207
with test_library as t:
204-
resp = t.config()
208+
agent_url = _trace_agent_url(t)
205209

206-
url = urlparse(resp["dd_trace_agent_url"])
210+
url = urlparse(agent_url)
207211
assert url.scheme == "http"
208212
assert url.hostname == "::1"
209213
assert url.port == 5000
@@ -217,8 +221,11 @@ class Test_Config_RateLimit:
217221
# which would be unreliable for testing and require significant effort for each tracer's weblog application.
218222
# The feature is mainly tested in the second test case, where the rate limit is set to 1 to ensure it works as expected.
219223
@parametrize("library_env", [{"DD_TRACE_SAMPLE_RATE": "1"}])
220-
def test_default_trace_rate_limit(self, test_library: APMLibrary):
224+
def test_default_trace_rate_limit(self, test_agent: TestAgentAPI, test_library: APMLibrary):
221225
with test_library as t:
226+
if t.lang == "nodejs":
227+
assert_nodejs_telemetry_config(test_agent, {"dd_trace_rate_limit": "100"})
228+
return
222229
resp = t.config()
223230
assert resp["dd_trace_rate_limit"] == "100"
224231

@@ -359,27 +366,41 @@ class Test_Config_Dogstatsd:
359366
@parametrize(
360367
"library_env", [{"DD_AGENT_HOST": "localhost"}]
361368
) # Adding DD_AGENT_HOST because some SDKs use DD_AGENT_HOST to set the dogstatsd host if unspecified
362-
def test_dogstatsd_default(self, test_library: APMLibrary):
369+
def test_dogstatsd_default(self, test_agent: TestAgentAPI, test_library: APMLibrary):
363370
with test_library as t:
371+
if t.lang == "nodejs":
372+
assert_nodejs_telemetry_config(
373+
test_agent, {"dd_dogstatsd_host": "localhost", "dd_dogstatsd_port": "8125"}
374+
)
375+
return
364376
resp = t.config()
365377
assert resp["dd_dogstatsd_host"] == "localhost"
366378
assert resp["dd_dogstatsd_port"] == "8125"
367379

368380
@parametrize("library_env", [{"DD_DOGSTATSD_HOST": "192.168.10.1"}])
369-
def test_dogstatsd_custom_ip_address(self, test_library: APMLibrary):
381+
def test_dogstatsd_custom_ip_address(self, test_agent: TestAgentAPI, test_library: APMLibrary):
370382
with test_library as t:
383+
if t.lang == "nodejs":
384+
assert_nodejs_telemetry_config(test_agent, {"dd_dogstatsd_host": "192.168.10.1"})
385+
return
371386
resp = t.config()
372387
assert resp["dd_dogstatsd_host"] == "192.168.10.1"
373388

374389
@parametrize("library_env", [{"DD_DOGSTATSD_HOST": "127.0.0.1"}])
375-
def test_dogstatsd_custom_hostname(self, test_library: APMLibrary):
390+
def test_dogstatsd_custom_hostname(self, test_agent: TestAgentAPI, test_library: APMLibrary):
376391
with test_library as t:
392+
if t.lang == "nodejs":
393+
assert_nodejs_telemetry_config(test_agent, {"dd_dogstatsd_host": "127.0.0.1"})
394+
return
377395
resp = t.config()
378396
assert resp["dd_dogstatsd_host"] == "127.0.0.1"
379397

380398
@parametrize("library_env", [{"DD_DOGSTATSD_PORT": "8150"}])
381-
def test_dogstatsd_custom_port(self, test_library: APMLibrary):
399+
def test_dogstatsd_custom_port(self, test_agent: TestAgentAPI, test_library: APMLibrary):
382400
with test_library as t:
401+
if t.lang == "nodejs":
402+
assert_nodejs_telemetry_config(test_agent, {"dd_dogstatsd_port": "8150"})
403+
return
383404
resp = t.config()
384405
assert resp["dd_dogstatsd_port"] == "8150"
385406

@@ -406,6 +427,17 @@ def test_dogstatsd_custom_port(self, test_library: APMLibrary):
406427
}
407428

408429

430+
def _trace_agent_url(test_library: APMLibrary) -> str:
431+
"""Resolve dd_trace_agent_url from the tracer's startup log (nodejs) or /trace/config (others).
432+
433+
These tests point the tracer at an unreachable agent, so telemetry never arrives; the
434+
published startup-config line is the non-internal source for the resolved URL.
435+
"""
436+
if test_library.lang == "nodejs":
437+
return str(nodejs_startup_config(test_library)["agent_url"])
438+
return test_library.config()["dd_trace_agent_url"] or ""
439+
440+
409441
class QuotedStr(str):
410442
__slots__ = ()
411443

@@ -489,6 +521,7 @@ class Test_Stable_Config_Default(StableConfigWriter):
489521
)
490522
def test_default_config(
491523
self,
524+
test_agent: TestAgentAPI,
492525
test_library: APMLibrary,
493526
path: str,
494527
name: str,
@@ -505,8 +538,11 @@ def test_default_config(
505538
test_library,
506539
)
507540
test_library.container_restart()
508-
config = test_library.config()
509-
assert expected.items() <= config.items()
541+
if test_library.lang == "nodejs":
542+
assert_nodejs_telemetry_config(test_agent, expected)
543+
else:
544+
config = test_library.config()
545+
assert expected.items() <= config.items()
510546

511547
@pytest.mark.parametrize("library_env", [{}])
512548
@pytest.mark.parametrize(
@@ -538,6 +574,7 @@ def test_default_config(
538574
)
539575
def test_extended_configs(
540576
self,
577+
test_agent: TestAgentAPI,
541578
test_library: APMLibrary,
542579
path: str,
543580
name: str,
@@ -561,6 +598,10 @@ def test_extended_configs(
561598
test_library,
562599
)
563600
test_library.container_restart()
601+
if test_library.lang == "nodejs":
602+
assert_nodejs_telemetry_config(test_agent, expected)
603+
return
604+
564605
config = test_library.config()
565606

566607
# Special handling for dd_tags: check if expected tags are present in actual tags
@@ -605,7 +646,7 @@ def test_extended_configs(
605646
"/etc/datadog-agent/application_monitoring.yaml",
606647
],
607648
)
608-
def test_unknown_key_skipped(self, test_library: APMLibrary, path: str, test: dict):
649+
def test_unknown_key_skipped(self, test_agent: TestAgentAPI, test_library: APMLibrary, path: str, test: dict):
609650
with test_library:
610651
self.write_stable_config(
611652
{
@@ -616,8 +657,11 @@ def test_unknown_key_skipped(self, test_library: APMLibrary, path: str, test: di
616657
test_library,
617658
)
618659
test_library.container_restart()
619-
config = test_library.config()
620-
assert test["expected"].items() <= config.items()
660+
if test_library.lang == "nodejs":
661+
assert_nodejs_telemetry_config(test_agent, test["expected"])
662+
else:
663+
config = test_library.config()
664+
assert test["expected"].items() <= config.items()
621665

622666
@pytest.mark.parametrize(
623667
"path",
@@ -626,16 +670,19 @@ def test_unknown_key_skipped(self, test_library: APMLibrary, path: str, test: di
626670
"/etc/datadog-agent/application_monitoring.yaml",
627671
],
628672
)
629-
def test_invalid_files(self, test_library: APMLibrary, path: str):
673+
def test_invalid_files(self, test_agent: TestAgentAPI, test_library: APMLibrary, path: str):
630674
with test_library:
631675
self.write_stable_config_content(
632676
"?? ??; ??\t\n\n --- `??",
633677
path,
634678
test_library,
635679
)
636680
test_library.container_restart()
637-
config = test_library.config()
638-
assert SDK_DEFAULT_STABLE_CONFIG.items() <= config.items()
681+
if test_library.lang == "nodejs":
682+
assert_nodejs_telemetry_config(test_agent, SDK_DEFAULT_STABLE_CONFIG)
683+
else:
684+
config = test_library.config()
685+
assert SDK_DEFAULT_STABLE_CONFIG.items() <= config.items()
639686

640687
@pytest.mark.parametrize(
641688
("name", "local_cfg", "library_env", "fleet_cfg", "expected"),
@@ -679,6 +726,7 @@ def test_invalid_files(self, test_library: APMLibrary, path: str):
679726
)
680727
def test_config_precedence(
681728
self,
729+
test_agent: TestAgentAPI,
682730
name: str,
683731
test_library: APMLibrary,
684732
local_cfg: dict,
@@ -703,6 +751,10 @@ def test_config_precedence(
703751
)
704752

705753
test_library.container_restart()
754+
if test_library.lang == "nodejs":
755+
assert_nodejs_telemetry_config(test_agent, expected)
756+
return
757+
706758
config = test_library.config()
707759
assert expected.items() <= config.items(), format(
708760
"unexpected values for the following configurations: {}"
@@ -716,7 +768,7 @@ class Test_Stable_Config_Rules(StableConfigWriter):
716768
"""Verify that stable config targeting rules work as intended (apm_configuration_rules)"""
717769

718770
@pytest.mark.parametrize("library_env", [{"STABLE_CONFIG_SELECTOR": "true", "DD_SERVICE": "not-my-service"}])
719-
def test_targeting_rules(self, test_library: APMLibrary):
771+
def test_targeting_rules(self, test_agent: TestAgentAPI, test_library: APMLibrary):
720772
path = "/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml"
721773
with test_library:
722774
self.write_stable_config(
@@ -739,18 +791,21 @@ def test_targeting_rules(self, test_library: APMLibrary):
739791
test_library,
740792
)
741793
test_library.container_restart()
742-
config = test_library.config()
743-
assert config["dd_service"] == "my-service", (
744-
f"Service name is '{config['dd_service']}' instead of 'my-service'"
745-
)
794+
if test_library.lang == "nodejs":
795+
assert_nodejs_telemetry_config(test_agent, {"dd_service": "my-service"})
796+
else:
797+
config = test_library.config()
798+
assert config["dd_service"] == "my-service", (
799+
f"Service name is '{config['dd_service']}' instead of 'my-service'"
800+
)
746801

747802
@pytest.mark.parametrize(
748803
"library_extra_command_arguments",
749804
[
750805
["-Darg1=value"]
751806
], # Note: This test was written for Java, so if this arg is not compatible for other libs, we may need to dynamically set library_extra_command_arguments based on context.library.name
752807
)
753-
def test_process_arguments(self, test_library: APMLibrary):
808+
def test_process_arguments(self, test_agent: TestAgentAPI, test_library: APMLibrary):
754809
path = "/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml"
755810
with test_library:
756811
config = {
@@ -772,7 +827,10 @@ def test_process_arguments(self, test_library: APMLibrary):
772827
stable_config_content = yaml.dump(config, Dumper=CustomDumper)
773828
self.write_stable_config_content(stable_config_content, path, test_library)
774829
test_library.container_restart()
775-
lib_config = test_library.config()
776-
assert lib_config["dd_service"] == "value", (
777-
f"Service name is '{lib_config['dd_service']}' instead of 'value'"
778-
)
830+
if test_library.lang == "nodejs":
831+
assert_nodejs_telemetry_config(test_agent, {"dd_service": "value"})
832+
else:
833+
lib_config = test_library.config()
834+
assert lib_config["dd_service"] == "value", (
835+
f"Service name is '{lib_config['dd_service']}' instead of 'value'"
836+
)

0 commit comments

Comments
 (0)