Skip to content

Commit 1489aaa

Browse files
committed
Merge branch 'main' of github.com:DataDog/system-tests into sabrenner/add-openai-llmobs-tests
2 parents 7cef61a + 6ea58fb commit 1489aaa

File tree

60 files changed

+726
-375
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+726
-375
lines changed

docs/scenarios/parametric.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This enables us to write unit/integration-style test cases that can be shared.
77
Example:
88

99
```python
10-
from utils.parametric.spec.trace import find_span, find_trace, find_span_in_traces, find_first_span_in_trace_payload, find_root_span
10+
from utils.docker_fixtures.spec.trace import find_span, find_trace, find_span_in_traces, find_first_span_in_trace_payload, find_root_span
1111

1212
@pytest.mark.parametrize("library_env", [{"DD_ENV": "prod"}])
1313
def test_datadog_spans(library_env, test_library, test_agent):

pyproject.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,6 @@ ignore = [
175175
"ANN201", # missing-return-type-undocumented-public-function: allow test_method to not declare their return type
176176
]
177177

178-
"tests/debugger/test_debugger_exception_replay.py" = [
179-
"ANN001", # missing-type-function-argument: TODO
180-
]
181178
"tests/*" = [
182179
"E501", # line-too-long: TODO
183180
"TRY002", # raise-vanilla-class: TODO

tests/appsec/iast/sink/test_header_injection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class TestHeaderInjection_StackTrace:
8484
def setup_stack_trace(self):
8585
self.r = weblog.post("/iast/header_injection/test_insecure", data={"test": "dummyvalue"})
8686

87+
@flaky(context.library >= "[email protected]", reason="APPSEC-59975")
8788
def test_stack_trace(self):
8889
validate_stack_traces(self.r)
8990

tests/appsec/iast/sink/test_no_samesite_cookie.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class TestNoSamesiteCookie_StackTrace:
6464
def setup_stack_trace(self):
6565
self.r = weblog.get("/iast/no-samesite-cookie/test_insecure")
6666

67+
@flaky(context.library >= "[email protected]", reason="APPSEC-59975")
6768
def test_stack_trace(self):
6869
validate_stack_traces(self.r)
6970

tests/debugger/test_debugger_exception_replay.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# This product includes software developed at Datadog (https://www.datadoghq.com/).
33
# Copyright 2021 Datadog, Inc.
44

5+
from collections.abc import Callable
56
import re
67
import os
78
import tests.debugger.utils as debugger
@@ -11,7 +12,7 @@
1112
from utils import scenarios, features, bug, context, flaky, irrelevant, missing_feature, logger
1213

1314

14-
def get_env_bool(env_var_name, *, default=False) -> bool:
15+
def get_env_bool(env_var_name: str, *, default: bool = False) -> bool:
1516
value = os.getenv(env_var_name, str(default)).lower()
1617
return value in {"true", "True", "1"}
1718

@@ -33,11 +34,11 @@ def get_env_bool(env_var_name, *, default=False) -> bool:
3334
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
3435
@irrelevant(context.library <= "[email protected]", reason="DEBUG-4582")
3536
class Test_Debugger_Exception_Replay(debugger.BaseDebuggerTest):
36-
snapshots: dict = {}
37+
snapshots: list[dict] = []
3738
spans: dict = {}
3839

3940
############ setup ############
40-
def _setup(self, request_path, exception_message):
41+
def _setup(self, request_path: str, exception_message: str):
4142
self.weblog_responses = []
4243

4344
retries = 0
@@ -54,7 +55,7 @@ def _setup(self, request_path, exception_message):
5455
retries += 1
5556

5657
############ assert ############
57-
def _assert(self, test_name, expected_exception_messages):
58+
def _assert(self, test_name: str, expected_exception_messages: list[str]):
5859
def __filter_contents_by_message():
5960
filtered_contents = []
6061

@@ -65,9 +66,9 @@ def __filter_contents_by_message():
6566
if expected_exception_message in exception_message:
6667
filtered_contents.append((exception_message, content))
6768

68-
def get_sort_key(content_tuple):
69+
def get_sort_key(content_tuple: tuple[str, dict]):
6970
message, content = content_tuple
70-
snapshot = content["debugger"]["snapshot"]
71+
snapshot: dict = content["debugger"]["snapshot"]
7172

7273
method_name = snapshot.get("probe", {}).get("location", {}).get("method", "")
7374
line_number = snapshot.get("probe", {}).get("location", {}).get("lines", [])
@@ -86,7 +87,7 @@ def get_sort_key(content_tuple):
8687

8788
return [snapshot for _, snapshot in filtered_contents]
8889

89-
def __filter_spans_by_snapshot_id(snapshots):
90+
def __filter_spans_by_snapshot_id(snapshots: list[dict]):
9091
filtered_spans = {}
9192

9293
for snapshot in snapshots:
@@ -103,7 +104,7 @@ def __filter_spans_by_snapshot_id(snapshots):
103104

104105
return filtered_spans
105106

106-
def __filter_spans_by_span_id(contents):
107+
def __filter_spans_by_span_id(contents: list[dict]):
107108
filtered_spans = {}
108109
for content in contents:
109110
span_id = content.get("dd", {}).get("span_id") or content.get("dd.span_id")
@@ -131,8 +132,13 @@ def __filter_spans_by_span_id(contents):
131132
spans = __filter_spans_by_snapshot_id(snapshots)
132133
self._validate_spans(test_name, spans)
133134

134-
def _validate_exception_replay_snapshots(self, test_name, snapshots):
135-
def __scrub(data):
135+
def _validate_exception_replay_snapshots(self, test_name: str, snapshots: list[dict]):
136+
def __scrub_dict(data: dict) -> dict:
137+
result = __scrub(data)
138+
assert isinstance(result, dict)
139+
return result
140+
141+
def __scrub(data: dict | list | str) -> dict | list | str:
136142
if isinstance(data, dict):
137143
scrubbed_data = {}
138144
for key, value in data.items():
@@ -149,10 +155,10 @@ def __scrub(data):
149155
else:
150156
return data
151157

152-
def __scrub_java(key, value, parent):
158+
def __scrub_java(key: str, value: dict | list, parent: dict):
153159
runtime = ("jdk.", "org.", "java")
154160

155-
def skip_runtime(value, skip_condition, del_filename=None):
161+
def skip_runtime(value: list, skip_condition: Callable, del_filename: Callable | None = None):
156162
scrubbed = []
157163

158164
for entry in value:
@@ -175,36 +181,41 @@ def skip_runtime(value, skip_condition, del_filename=None):
175181
return "<scrubbed>"
176182

177183
if parent["type"] == "java.lang.Object[]":
184+
assert isinstance(value, list)
178185
return skip_runtime(value, lambda e: "value" in e and e["value"].startswith(runtime))
179186

180187
if parent["type"] == "java.lang.StackTraceElement[]":
188+
assert isinstance(value, list)
181189
return skip_runtime(value, lambda e: e["fields"]["declaringClass"]["value"].startswith(runtime))
182190

183191
return __scrub(value)
184192

185193
elif key == "moduleVersion":
186194
return "<scrubbed>"
187195
elif key in ["stacktrace", "stack"]:
196+
assert isinstance(value, list)
188197
return skip_runtime(
189198
value, lambda e: "function" in e and e["function"].startswith(runtime), lambda e: "fileName" in e
190199
)
191200

192201
return __scrub(value)
193202

194-
def __scrub_dotnet(key, value, parent): # noqa: ARG001
203+
def __scrub_dotnet(key: str, value: dict | list | str, parent: dict): # noqa: ARG001
195204
if key == "Id":
196205
return "<scrubbed>"
197206
elif key == "StackTrace" and isinstance(value, dict):
198207
value["value"] = "<scrubbed>"
199208
return value
200209
elif key == "function":
210+
assert isinstance(value, str)
201211
if "lambda_" in value:
202212
value = re.sub(r"(lambda_method)\d+", r"\1<scrubbed>", value)
203213
if re.search(r"<[^>]+>", value):
204214
value = re.sub(r"(.*>)(.*)", r"\1<scrubbed>", value)
205215
return value
206216
elif key in ["stacktrace", "stack"]:
207217
scrubbed = []
218+
assert isinstance(value, list)
208219
for entry in value:
209220
# skip inner runtime methods from stack traces since they are not relevant to debugger
210221
if entry["function"].startswith(("Microsoft", "System", "Unknown")):
@@ -216,8 +227,9 @@ def __scrub_dotnet(key, value, parent): # noqa: ARG001
216227
return scrubbed
217228
return __scrub(value)
218229

219-
def __scrub_python(key, value, parent): # noqa: ARG001
230+
def __scrub_python(key: str, value: dict | list, parent: dict): # noqa: ARG001
220231
if key == "@exception":
232+
assert isinstance(value, dict)
221233
value["fields"] = "<scrubbed>"
222234
return value
223235

@@ -241,7 +253,7 @@ def __scrub_python(key, value, parent): # noqa: ARG001
241253

242254
return __scrub(value)
243255

244-
def __scrub_none(key, value, parent): # noqa: ARG001
256+
def __scrub_none(key: str, value: dict | list, parent: dict): # noqa: ARG001
245257
return __scrub(value)
246258

247259
if self.get_tracer()["language"] == "java":
@@ -253,7 +265,7 @@ def __scrub_none(key, value, parent): # noqa: ARG001
253265
else:
254266
scrub_language = __scrub_none
255267

256-
def __approve(snapshots):
268+
def __approve(snapshots: list):
257269
self._write_approval(snapshots, test_name, "snapshots_received")
258270

259271
if _OVERRIDE_APROVALS or _STORE_NEW_APPROVALS:
@@ -268,14 +280,14 @@ def __approve(snapshots):
268280
assert snapshots, "Snapshots not found"
269281

270282
if not _SKIP_SCRUB:
271-
snapshots = [__scrub(snapshot) for snapshot in snapshots]
283+
snapshots = [__scrub_dict(snapshot) for snapshot in snapshots]
272284

273285
self.snapshots = snapshots
274286
__approve(snapshots)
275287

276-
def _validate_spans(self, test_name: str, spans):
277-
def __scrub(data):
278-
def scrub_span(key, value):
288+
def _validate_spans(self, test_name: str, spans: dict[str, dict]):
289+
def __scrub(data: dict[str, dict]) -> dict[str, dict]:
290+
def scrub_span(key: str, value: dict | list):
279291
if key in {"traceID", "spanID", "parentID", "start", "duration", "metrics"}:
280292
return "<scrubbed>"
281293

@@ -320,7 +332,7 @@ def scrub_span(key, value):
320332

321333
return scrubbed_spans
322334

323-
def __approve(spans):
335+
def __approve(spans: dict[str, dict]):
324336
self._write_approval(spans, test_name, "spans_received")
325337

326338
if _OVERRIDE_APROVALS or _STORE_NEW_APPROVALS:
@@ -356,15 +368,15 @@ def __approve(spans):
356368
self.spans = spans
357369
__approve(spans)
358370

359-
def _validate_recursion_snapshots(self, snapshots, limit):
371+
def _validate_recursion_snapshots(self, snapshots: list[dict], limit: int):
360372
assert len(snapshots) == limit + 1, (
361373
f"Expected {limit + 1} snapshots for recursion limit {limit}, got {len(snapshots)}"
362374
)
363375

364376
entry_method = "exceptionReplayRecursion"
365377
helper_method = "exceptionReplayRecursionHelper"
366378

367-
def get_frames(snapshot):
379+
def get_frames(snapshot: dict) -> list[dict]:
368380
if self.get_tracer()["language"] in ["java", "dotnet"]:
369381
method = snapshot.get("probe", {}).get("location", {}).get("method", None)
370382
if method:
@@ -375,7 +387,7 @@ def get_frames(snapshot):
375387
found_top = False
376388
found_lowest = False
377389

378-
def check_frames(frames):
390+
def check_frames(frames: list[dict]):
379391
nonlocal found_top, found_lowest
380392

381393
for frame in frames:
@@ -453,7 +465,7 @@ def _get_approval_version(self) -> str:
453465
compatible_versions.sort(key=lambda x: version.parse(x), reverse=True)
454466
return compatible_versions[0]
455467

456-
def _write_approval(self, data: list, test_name: str, suffix: str) -> None:
468+
def _write_approval(self, data: list | dict, test_name: str, suffix: str) -> None:
457469
"""Write approval data to version-aware path."""
458470
version = self._get_approval_version()
459471
debugger.write_approval(data, test_name, suffix, version)

tests/debugger/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _get_path(test_name: str, suffix: str, version: str) -> str:
7474
return os.path.join(_CUR_DIR, "utils", "approvals", language, version, filename)
7575

7676

77-
def write_approval(data: list, test_name: str, suffix: str, version: str) -> None:
77+
def write_approval(data: list | dict, test_name: str, suffix: str, version: str) -> None:
7878
path = _get_path(test_name, suffix, version)
7979
Path(path).parent.mkdir(parents=True, exist_ok=True)
8080
with open(path, "w", encoding="utf-8") as f:

tests/parametric/conftest.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
import pytest
99
import yaml
1010

11-
from utils.parametric._library_client import APMLibrary
12-
1311
from utils import scenarios, logger
12+
from utils.docker_fixtures import TestAgentAPI, ParametricTestClientApi as APMLibrary
1413

15-
from utils.docker_fixtures import TestAgentAPI as _TestAgentAPI
1614

1715
# Max timeout in seconds to keep a container running
1816
default_subprocess_run_timeout = 300
@@ -77,7 +75,7 @@ def test_agent(
7775
request: pytest.FixtureRequest,
7876
test_agent_otlp_http_port: int,
7977
test_agent_otlp_grpc_port: int,
80-
) -> Generator[_TestAgentAPI, None, None]:
78+
) -> Generator[TestAgentAPI, None, None]:
8179
with scenarios.parametric.get_test_agent_api(
8280
request=request,
8381
worker_id=worker_id,
@@ -93,7 +91,7 @@ def test_library(
9391
worker_id: str,
9492
request: pytest.FixtureRequest,
9593
test_id: str,
96-
test_agent: _TestAgentAPI,
94+
test_agent: TestAgentAPI,
9795
library_env: dict[str, str],
9896
library_extra_command_arguments: list[str],
9997
) -> Generator[APMLibrary, None, None]:

tests/parametric/test_128_bit_traceids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from utils.parametric.spec.trace import find_first_span_in_trace_payload, find_trace, find_only_span
3+
from utils.docker_fixtures.spec.trace import find_first_span_in_trace_payload, find_trace, find_only_span
44
from utils import missing_feature, irrelevant, context, scenarios, features
55
from utils.docker_fixtures import TestAgentAPI
66
from .conftest import APMLibrary

tests/parametric/test_config_consistency.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
import yaml
77
from utils import scenarios, features, context, missing_feature, irrelevant, flaky, bug, rfc, incomplete_test_app
8-
from utils.parametric.spec.trace import find_span_in_traces, find_only_span
8+
from utils.docker_fixtures.spec.trace import find_span_in_traces, find_only_span
99
from utils.docker_fixtures import TestAgentAPI
1010
from .conftest import APMLibrary, StableConfigWriter
1111

@@ -663,6 +663,7 @@ def test_unknown_key_skipped(
663663
"/etc/datadog-agent/application_monitoring.yaml",
664664
],
665665
)
666+
@bug(context.library <= "[email protected]", reason="APMAPI-1774")
666667
def test_invalid_files(
667668
self, test_agent: TestAgentAPI, test_library: APMLibrary, path: str, library_env: dict[str, str]
668669
):

tests/parametric/test_crashtracking.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import pytest
66

77
from utils import bug, features, scenarios, logger
8-
from utils.parametric._library_client import APMLibrary
9-
from utils.docker_fixtures import TestAgentAPI
8+
from utils.docker_fixtures import TestAgentAPI, ParametricTestClientApi as APMLibrary
109

1110

1211
@scenarios.parametric
@@ -44,7 +43,7 @@ def test_telemetry_timeout(self, test_agent: TestAgentAPI, test_library: APMLibr
4443

4544
try:
4645
# container.wait will throw if the application doesn't exit in time
47-
test_library._client.container.wait(timeout=10) # noqa: SLF001
46+
test_library.container.wait(timeout=10)
4847
finally:
4948
test_agent.set_trace_delay(0)
5049

0 commit comments

Comments
 (0)